From fe8270010350f9d1c215d78b5e2db0702273c1dc Mon Sep 17 00:00:00 2001
From: gpawel <gpawel>
Date: Tue, 30 Aug 2011 12:45:31 +0000
Subject: [PATCH] LMS-2462 import from Excel files

SVN: 22710
---
 .../cisd/common/filesystem/FileUtilities.java |   3 +-
 .../common/parser/DefaultLineTokenizer.java   |   6 +-
 .../cisd/common/parser/DefaultParser.java     |  28 +-
 .../cisd/common/parser/ExcelFileLoader.java   | 366 ++++++++++++++++++
 .../systemsx/cisd/common/parser/ExcelRow.java |  69 ++++
 .../cisd/common/parser/ExcelRowTokenizer.java |  87 +++++
 .../cisd/common/parser/HeaderLineFilter.java  |   6 +-
 .../ch/systemsx/cisd/common/parser/ILine.java |  31 ++
 .../cisd/common/parser/ILineTokenizer.java    |   8 +-
 .../systemsx/cisd/common/parser/IParser.java  |   6 +-
 .../ch/systemsx/cisd/common/parser/Line.java  |   9 +-
 .../cisd/common/parser/ParserUtilities.java   |  16 +-
 .../cisd/common/parser/TabFileLoader.java     |  84 ++--
 .../parser/filter/AlwaysAcceptLineFilter.java |   4 +-
 .../ExcludeEmptyAndCommentLineFilter.java     |   6 +-
 .../common/parser/filter/ILineFilter.java     |   4 +-
 .../parser/filter/NonEmptyLineFilter.java     |   6 +-
 .../cisd/common/parser/DefaultParserTest.java |  78 ++--
 .../common/parser/HeaderLineFilterTest.java   |  22 +-
 .../common/parser/ParserUtilitiesTest.java    |  14 +-
 .../java/MigrationStepExecutor.java           |  10 +-
 .../shared/parser/BisExcelFileLoader.java     | 109 ++++++
 .../shared/parser/ExcelFileSection.java       | 167 ++++++++
 .../parser/SampleUploadSectionsParser.java    | 146 +++++--
 .../parser/MaterialUploadSectionsParser.java  | 147 +++++--
 25 files changed, 1211 insertions(+), 221 deletions(-)
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/ExcelFileLoader.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/ExcelRow.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/ExcelRowTokenizer.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/ILine.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/BisExcelFileLoader.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/ExcelFileSection.java

diff --git a/common/source/java/ch/systemsx/cisd/common/filesystem/FileUtilities.java b/common/source/java/ch/systemsx/cisd/common/filesystem/FileUtilities.java
index dbe472c8751..2aed775d097 100644
--- a/common/source/java/ch/systemsx/cisd/common/filesystem/FileUtilities.java
+++ b/common/source/java/ch/systemsx/cisd/common/filesystem/FileUtilities.java
@@ -59,6 +59,7 @@ import ch.systemsx.cisd.common.exceptions.FileExistsException;
 import ch.systemsx.cisd.common.exceptions.UnknownLastChangedException;
 import ch.systemsx.cisd.common.logging.ISimpleLogger;
 import ch.systemsx.cisd.common.logging.LogLevel;
+import ch.systemsx.cisd.common.parser.Line;
 import ch.systemsx.cisd.common.parser.filter.AlwaysAcceptLineFilter;
 import ch.systemsx.cisd.common.parser.filter.ILineFilter;
 import ch.systemsx.cisd.common.utilities.StringUtilities;
@@ -410,7 +411,7 @@ public final class FileUtilities
             String line = reader.readLine();
             for (int lineNumber = 0; line != null; ++lineNumber, line = reader.readLine())
             {
-                if (lineFilter.acceptLine(line, lineNumber))
+                if (lineFilter.acceptLine(new Line(lineNumber, line)))
                 {
                     list.add(line);
                 }
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/DefaultLineTokenizer.java b/common/source/java/ch/systemsx/cisd/common/parser/DefaultLineTokenizer.java
index db16bc4b767..394bb680603 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/DefaultLineTokenizer.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/DefaultLineTokenizer.java
@@ -32,7 +32,7 @@ import org.apache.commons.lang.text.StrTokenizer;
  * 
  * @author Christian Ribeaud
  */
-public class DefaultLineTokenizer implements ILineTokenizer
+public class DefaultLineTokenizer implements ILineTokenizer<String>
 {
 
     /** Allowed <code>Properties</code> keys. */
@@ -82,8 +82,8 @@ public class DefaultLineTokenizer implements ILineTokenizer
     /**
      * Sets a property for this <code>TabReaderParser</code>.
      * <p>
-     * Does nothing if given <code>key</code> is <code>null</code> and resets <code>key</code>
-     * to default value if given <code>value</code> is <code>null</code>.
+     * Does nothing if given <code>key</code> is <code>null</code> and resets <code>key</code> to
+     * default value if given <code>value</code> is <code>null</code>.
      * </p>
      */
     public final void setProperty(PropertyKey key, String value)
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/DefaultParser.java b/common/source/java/ch/systemsx/cisd/common/parser/DefaultParser.java
index e715fc77493..014209e81f0 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/DefaultParser.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/DefaultParser.java
@@ -32,24 +32,24 @@ import ch.systemsx.cisd.common.parser.filter.ILineFilter;
  * 
  * @author Christian Ribeaud
  */
-public class DefaultParser<E> implements IParser<E>
+public class DefaultParser<E, T> implements IParser<E, T>
 {
-    private final ILineTokenizer lineTokenizer;
+    private final ILineTokenizer<T> lineTokenizer;
 
     private IParserObjectFactory<E> factory;
 
     /**
      * Creates an instance based on the {@link DefaultLineTokenizer}.
      */
-    public DefaultParser()
+    public static <E> DefaultParser<E, String> createDefaultParser()
     {
-        this(new DefaultLineTokenizer());
+        return new DefaultParser<E, String>(new DefaultLineTokenizer());
     }
 
     /**
      * Creates an instance for the specified line tokenizer.
      */
-    public DefaultParser(final ILineTokenizer lineTokenizer)
+    public DefaultParser(final ILineTokenizer<T> lineTokenizer)
     {
         this.lineTokenizer = lineTokenizer;
     }
@@ -59,17 +59,17 @@ public class DefaultParser<E> implements IParser<E>
         return factory.createObject(tokens);
     }
 
-    public final List<E> parse(final Iterator<Line> lineIterator, final ILineFilter lineFilter,
+    public final List<E> parse(final Iterator<ILine<T>> lineIterator, final ILineFilter lineFilter,
             final int headerLength) throws ParsingException
     {
         final List<E> elements = new ArrayList<E>();
         lineTokenizer.init();
         while (lineIterator.hasNext())
         {
-            final Line line = lineIterator.next();
-            final String nextLine = line.getText();
+            final ILine<T> line = lineIterator.next();
+            final T nextLine = line.getObject();
             final int number = line.getNumber();
-            if (lineFilter.acceptLine(nextLine, number))
+            if (lineFilter.acceptLine(line))
             {
                 E object = null;
                 String[] tokens = parseLine(number, nextLine, headerLength);
@@ -91,13 +91,13 @@ public class DefaultParser<E> implements IParser<E>
         return elements;
     }
 
-    public final Iterator<E> parseIteratively(final Iterator<Line> lineIterator,
+    public final Iterator<E> parseIteratively(final Iterator<ILine<T>> lineIterator,
             final ILineFilter lineFilter, final int headerLength) throws ParsingException
     {
         lineTokenizer.init();
         return new Iterator<E>()
             {
-                Line currentLine = null;
+                ILine<T> currentLine = null;
 
                 public boolean hasNext()
                 {
@@ -105,7 +105,7 @@ public class DefaultParser<E> implements IParser<E>
                     while (hasNext)
                     {
                         currentLine = lineIterator.next();
-                        if (lineFilter.acceptLine(currentLine.getText(), currentLine.getNumber()))
+                        if (lineFilter.acceptLine(currentLine))
                         {
                             break;
                         }
@@ -125,7 +125,7 @@ public class DefaultParser<E> implements IParser<E>
                     {
                         throw new NoSuchElementException();
                     }
-                    final String nextLine = currentLine.getText();
+                    final T nextLine = currentLine.getObject();
                     final int number = currentLine.getNumber();
                     currentLine = null;
                     String[] tokens = parseLine(number, nextLine, headerLength);
@@ -151,7 +151,7 @@ public class DefaultParser<E> implements IParser<E>
         this.factory = factory;
     }
 
-    private String[] parseLine(final int lineNumber, final String nextLine, final int headerLength)
+    private String[] parseLine(final int lineNumber, final T nextLine, final int headerLength)
     {
         String[] tokens = lineTokenizer.tokenize(nextLine);
         if (tokens.length > headerLength)
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ExcelFileLoader.java b/common/source/java/ch/systemsx/cisd/common/parser/ExcelFileLoader.java
new file mode 100644
index 00000000000..c63545e93e2
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ExcelFileLoader.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+
+import ch.systemsx.cisd.common.exceptions.NotImplementedException;
+import ch.systemsx.cisd.common.parser.filter.AlwaysAcceptLineFilter;
+import ch.systemsx.cisd.common.parser.filter.ILineFilter;
+
+/**
+ * Convenient class to load (or iterate over) a tab file, a reader or a stream. The loader delivers
+ * either a list or an iterator of beans of type <code>T</code>. The following formats for the
+ * column headers are recognized.
+ * <ol>
+ * <li>Column headers in first line:
+ * 
+ * <pre>
+ *     column1  column2 column2
+ * </pre>
+ * <li>Comment section:
+ * 
+ * <pre>
+ *     # 1. line of comment
+ *     # 2. line of comment
+ *     # ...
+ *     column1  column2 column2
+ * </pre>
+ * <li>Column headers at the end of the comment section:
+ * 
+ * <pre>
+ *     # 1. line of comment
+ *     # 2. line of comment
+ *     # ...
+ *     #
+ *     #column1  column2 column2
+ * </pre>
+ * 
+ * </ol>
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class ExcelFileLoader<T>
+{
+    public static final String COMMENT_PREFIX = "#";
+
+    // Excel can put comments in double quotes (") if they contain quote character. Multiple saving
+    // of the can cause multiple '"' to occure before the '#' character.
+    public static final Pattern COMMENT_REGEXP_PATTERN = Pattern.compile("\"*#.*");
+
+    public static final String DEFAULT_SECTION = "[DEFAULT]";
+
+    private final IParserObjectFactoryFactory<T> factory;
+
+    /**
+     * Creates a new instance based on the factory which uses only bean annotations.
+     */
+    public ExcelFileLoader(final Class<T> beanClass)
+    {
+        this.factory = new IParserObjectFactoryFactory<T>()
+            {
+                public IParserObjectFactory<T> createFactory(IPropertyMapper propertyMapper)
+                        throws ParserException
+                {
+                    return new AbstractParserObjectFactory<T>(beanClass, propertyMapper)
+                        {
+                        };
+                }
+            };
+    }
+
+    /**
+     * Creates a new instance based on the specified factory.
+     */
+    public ExcelFileLoader(final IParserObjectFactoryFactory<T> factory)
+    {
+        assert factory != null : "Undefined factory";
+        this.factory = factory;
+    }
+
+    /**
+     * Loads data from the specified reader.
+     * <p>
+     * The header can contain comments which are ignored. The column names can be the first
+     * uncommented line or the last commented line. The latter case is determined by the fact, that
+     * the one before the last line is a single hash.
+     * </p>
+     */
+    public List<T> load(final Sheet sheet, int begin, int end, Map<String, String> defaults)
+            throws ParserException, ParsingException, IllegalArgumentException
+    {
+        assert sheet != null : "Unspecified reader";
+
+        final Iterator<ILine<Row>> rowIterator = createRowIterator(sheet, begin, end);
+        return load(rowIterator, defaults);
+    }
+
+    public static final Map<String, String> parseDefaults(Sheet sheet, int begin, int end)
+    {
+        final Iterator<ILine<Row>> lineIterator = createRowIterator(sheet, begin, end);
+        final Map<String, String> defaults = new HashMap<String, String>();
+        return parseDefaults(lineIterator, defaults);
+    }
+
+    private static final Map<String, String> parseDefaults(final Iterator<ILine<Row>> rowIterator,
+            final Map<String, String> defaults)
+    {
+        while (rowIterator.hasNext())
+        {
+            ILine<Row> row = rowIterator.next();
+            if (startsDefaultSection(row))
+            {
+                break;
+            }
+
+            String[] tokens = ExcelRowTokenizer.tokenizeRow(row.getObject());
+            if (tokens.length >= 2)
+            {
+                String name = tokens[0];
+                String value = tokens[1];
+                defaults.put(name.toLowerCase(), value);
+            }
+        }
+
+        return defaults;
+    }
+
+    private List<T> load(final Iterator<ILine<Row>> lineIterator, Map<String, String> fileDefaults)
+    {
+        ILine<Row> previousLine = null;
+        ILine<Row> line = null;
+        boolean previousLineHasColumnHeaders = false;
+
+        Map<String, String> defaults = new HashMap<String, String>(fileDefaults);
+        while (lineIterator.hasNext())
+        {
+            previousLineHasColumnHeaders = (previousLine != null) && isComment(previousLine);
+            previousLine = line;
+            line = lineIterator.next();
+            if (isComment(line))
+            {
+                continue;
+            } else if (startsDefaultSection(line))
+            {
+                parseDefaults(lineIterator, defaults);
+                line = previousLine = null;
+                previousLineHasColumnHeaders = false;
+            } else if (isComment(line) == false)
+            {
+                break;
+            }
+        }
+
+        if (line == null) // no lines present
+        {
+            return new ArrayList<T>();
+        }
+
+        Row headerRow = null;
+        if (previousLineHasColumnHeaders && (previousLine != null /* just for eclipse */))
+        {
+            headerRow = trimComment(previousLine);
+        } else
+        {
+            headerRow = line.getObject();
+        }
+
+        final IParser<T, Row> parser = createParser();
+        final String[] tokens = ExcelRowTokenizer.tokenizeRow(headerRow);
+        final int headerLength = tokens.length;
+        notUnique(tokens);
+
+        final IPropertyMapper propertyMapper = new DefaultPropertyMapper(tokens, defaults);
+        parser.setObjectFactory(factory.createFactory(propertyMapper));
+
+        ILine<Row> firstContentLine = previousLineHasColumnHeaders ? line : null;
+        Iterator<ILine<Row>> contentLineIterator =
+                createContentIterator(firstContentLine, lineIterator);
+        final ILineFilter filter = AlwaysAcceptLineFilter.INSTANCE;
+        return parser.parse(contentLineIterator, filter, headerLength);
+    }
+
+    private static boolean startsDefaultSection(Row row)
+    {
+        Cell cell = row.getCell(0);
+        if (cell == null || cell.getCellType() != Cell.CELL_TYPE_STRING)
+        {
+            return false;
+        } else
+        {
+            return DEFAULT_SECTION.equals(cell.getStringCellValue());
+        }
+    }
+
+    private static boolean startsDefaultSection(ILine<Row> row)
+    {
+        return startsDefaultSection(row.getObject());
+    }
+
+    private static Row trimComment(ILine<Row> previousLine)
+    {
+        if (isComment(previousLine))
+        {
+            Cell firstCell = previousLine.getObject().getCell(0);
+            firstCell.setCellValue(firstCell.getStringCellValue()
+                    .substring(COMMENT_PREFIX.length()));
+        }
+        return previousLine.getObject();
+    }
+
+    private static boolean isComment(ILine<Row> line)
+    {
+        Row row = line.getObject();
+        return row.getCell(0) != null && row.getCell(0).toString().startsWith(COMMENT_PREFIX);
+    }
+
+    /**
+     * @param firstContentLineOrNull if not null, it will be returned as the first iterator element,
+     *            followed by all iterator elements from the second parameter
+     */
+    private static Iterator<ILine<Row>> createContentIterator(
+            final ILine<Row> firstContentLineOrNull, final Iterator<ILine<Row>> lineIterator)
+    {
+        return new Iterator<ILine<Row>>()
+            {
+                private ILine<Row> firstLineOrNull = firstContentLineOrNull;
+
+                public boolean hasNext()
+                {
+                    return firstLineOrNull != null || lineIterator.hasNext();
+                }
+
+                public ILine<Row> next()
+                {
+                    return nextLine();
+                }
+
+                private ILine<Row> nextLine()
+                {
+                    if (firstLineOrNull != null)
+                    {
+                        ILine<Row> line = firstLineOrNull;
+                        firstLineOrNull = null;
+                        return line;
+                    } else
+                    {
+                        return lineIterator.next();
+                    }
+                }
+
+                public void remove()
+                {
+                    throw new NotImplementedException();
+                }
+            };
+    }
+
+    private static Iterator<ILine<Row>> createRowIterator(Sheet sheet, int begin, int end)
+    {
+        return new ExcelFileRowIterator(sheet, begin, end);
+    }
+
+    /**
+     * Checks given <var>tokens</var> whether there is no duplicate.
+     * <p>
+     * Note that the search is case-insensitive.
+     * </p>
+     * 
+     * @throws IllegalArgumentException if there is at least one duplicate in the given
+     *             <var>tokens</var>.
+     */
+    private final static void notUnique(final String[] tokens)
+    {
+        assert tokens != null : "Given tokens can not be null.";
+        final Set<String> unique = new HashSet<String>();
+        for (final String token : tokens)
+        {
+            if (unique.add(token.toLowerCase()) == false)
+            {
+                throw new IllegalArgumentException(String.format("Duplicated column name '%s'.",
+                        token));
+            }
+        }
+    }
+
+    private final <E> IParser<E, Row> createParser()
+    {
+        ExcelRowTokenizer tokenizer = new ExcelRowTokenizer();
+        return new DefaultParser<E, Row>(tokenizer);
+    }
+
+    //
+    // Helper classes
+    //
+    private final static class ExcelFileRowIterator implements Iterator<ILine<Row>>
+    {
+        private final Sheet sheet;
+
+        private int current;
+
+        private final int end;
+
+        private ExcelFileRowIterator(Sheet sheet, int begin, int end)
+        {
+            this.sheet = sheet;
+            this.current = begin;
+            this.end = end;
+            getFirstNonEmptyCurrent();
+        }
+
+        private void getFirstNonEmptyCurrent()
+        {
+            while (sheet.getRow(current) == null && current <= end)
+            {
+                current++;
+            }
+        }
+
+        public final void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        public final ILine<Row> next()
+        {
+            try
+            {
+                return new ExcelRow(sheet.getRow(current));
+            } finally
+            {
+                current++;
+                getFirstNonEmptyCurrent();
+            }
+        }
+
+        public final boolean hasNext()
+        {
+            return current <= end;
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ExcelRow.java b/common/source/java/ch/systemsx/cisd/common/parser/ExcelRow.java
new file mode 100644
index 00000000000..545a43d51e7
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ExcelRow.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class ExcelRow implements ILine<Row>
+{
+    private Row row;
+
+    private String text;
+
+    public ExcelRow(Row row)
+    {
+        this.row = row;
+    }
+
+    public String getText()
+    {
+        if (text == null)
+        {
+            text = toString(row);
+        }
+        return text;
+    }
+
+    public int getNumber()
+    {
+        return row.getRowNum();
+    }
+
+    public Row getObject()
+    {
+        return row;
+    }
+
+    private static String toString(Row row)
+    {
+        StringBuilder sb = new StringBuilder();
+
+        for (String s : new ExcelRowTokenizer().tokenize(row))
+        {
+            if (s.length() > 0)
+            {
+                sb.append("\t");
+            }
+            sb.append(s);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ExcelRowTokenizer.java b/common/source/java/ch/systemsx/cisd/common/parser/ExcelRowTokenizer.java
new file mode 100644
index 00000000000..5be83b61689
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ExcelRowTokenizer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.util.CellReference;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class ExcelRowTokenizer implements ILineTokenizer<Row>
+{
+    public void init()
+    {
+    }
+
+    public String[] tokenize(Row row)
+    {
+        return tokenizeRow(row);
+    }
+
+    public static String[] tokenizeRow(Row row)
+    {
+        String[] line = new String[row.getLastCellNum()];
+        for (Cell cell : row)
+        {
+            String value = extractCellValue(cell);
+            line[cell.getColumnIndex()] = value;
+        }
+
+        return line;
+    }
+
+    private static String extractCellValue(Cell cell)
+    {
+        switch (cell.getCellType())
+        {
+            case Cell.CELL_TYPE_BLANK:
+                return "BLANK";
+            case Cell.CELL_TYPE_BOOLEAN:
+                return Boolean.toString(cell.getBooleanCellValue());
+            case Cell.CELL_TYPE_NUMERIC:
+                return Double.toString(cell.getNumericCellValue());
+            case Cell.CELL_TYPE_STRING:
+                return cell.getStringCellValue();
+            case Cell.CELL_TYPE_FORMULA:
+                throw new UserFailureException(
+                        "Excel formulas are not supported but one was found in cell "
+                                + extractCellPosition(cell));
+            case Cell.CELL_TYPE_ERROR:
+                throw new UserFailureException("There is an error in cell "
+                        + extractCellPosition(cell));
+            default:
+                throw new UserFailureException("Unknown data type of cell "
+                        + extractCellPosition(cell));
+        }
+    }
+
+    /** @return cell coordinates in Excel style (e.g. D5) */
+    private static String extractCellPosition(Cell cell)
+    {
+        String col = CellReference.convertNumToColString(cell.getColumnIndex());
+        String row = "" + (cell.getRowIndex() + 1);
+        return col + row;
+    }
+
+    public void destroy()
+    {
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/HeaderLineFilter.java b/common/source/java/ch/systemsx/cisd/common/parser/HeaderLineFilter.java
index 5701c069f1b..08837e5ebc1 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/HeaderLineFilter.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/HeaderLineFilter.java
@@ -54,10 +54,10 @@ public final class HeaderLineFilter implements ILineFilter
     // LineFilter
     //
 
-    public final boolean acceptLine(String line, int lineNumber)
+    public final <T> boolean acceptLine(ILine<T> line)
     {
-        if (ExcludeEmptyAndCommentLineFilter.INSTANCE.acceptLine(line, lineNumber) == false
-                || lineNumber == headerLineNumber)
+        if (ExcludeEmptyAndCommentLineFilter.INSTANCE.acceptLine(line) == false
+                || line.getNumber() == headerLineNumber)
         {
             return false;
         }
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ILine.java b/common/source/java/ch/systemsx/cisd/common/parser/ILine.java
new file mode 100644
index 00000000000..db46e9cd992
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ILine.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.parser;
+
+/**
+ * A small object that represents a line in a <code>File</code> context.
+ * 
+ * @author Pawel Glyzewski
+ */
+public interface ILine<T>
+{
+    public String getText();
+
+    public int getNumber();
+
+    public T getObject();
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ILineTokenizer.java b/common/source/java/ch/systemsx/cisd/common/parser/ILineTokenizer.java
index de749399a5d..91b7f82d504 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/ILineTokenizer.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ILineTokenizer.java
@@ -17,12 +17,12 @@
 package ch.systemsx.cisd.common.parser;
 
 /**
- * A <code>ILineTokenizer</code> implementation is able to split a given <code>String</code>
- * line in an array of tokens.
+ * A <code>ILineTokenizer</code> implementation is able to split a given <code>T</code> line in an
+ * array of tokens.
  * 
  * @author Christian Ribeaud
  */
-public interface ILineTokenizer
+public interface ILineTokenizer<T>
 {
 
     /**
@@ -34,7 +34,7 @@ public interface ILineTokenizer
     public void init();
 
     /** Splits given <code>line</code> into an array of tokens. */
-    public String[] tokenize(String line);
+    public String[] tokenize(T line);
 
     /**
      * Cleans up resources used by this <code>ILineTokenizer</code>.
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/IParser.java b/common/source/java/ch/systemsx/cisd/common/parser/IParser.java
index 62de1595e0e..8912dff0c8c 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/IParser.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/IParser.java
@@ -27,7 +27,7 @@ import ch.systemsx.cisd.common.parser.filter.ILineFilter;
  * 
  * @author Christian Ribeaud
  */
-public interface IParser<E>
+public interface IParser<E, T>
 {
 
     /**
@@ -38,7 +38,7 @@ public interface IParser<E>
      * @param headerLength number of columns in the header
      * @return a <code>List</code> of elements.
      */
-    public List<E> parse(final Iterator<Line> lineIterator, final ILineFilter lineFilter,
+    public List<E> parse(final Iterator<ILine<T>> lineIterator, final ILineFilter lineFilter,
             final int headerLength) throws ParsingException;
 
     /**
@@ -51,7 +51,7 @@ public interface IParser<E>
      * @param headerLength number of columns in the header
      * @return an <code>List</code> of elements.
      */
-    public Iterator<E> parseIteratively(final Iterator<Line> lineIterator,
+    public Iterator<E> parseIteratively(final Iterator<ILine<T>> lineIterator,
             final ILineFilter lineFilter, final int headerLength) throws ParsingException;
 
     /**
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/Line.java b/common/source/java/ch/systemsx/cisd/common/parser/Line.java
index dfcd0d41157..2e8745e063e 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/Line.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/Line.java
@@ -21,13 +21,13 @@ package ch.systemsx.cisd.common.parser;
  * 
  * @author Christian Ribeaud
  */
-public final class Line
+public final class Line implements ILine<String>
 {
     private final String text;
 
     private final int number;
 
-    Line(final int number, final String text)
+    public Line(final int number, final String text)
     {
         assert text != null : "Unspecified text.";
         this.number = number;
@@ -44,4 +44,9 @@ public final class Line
         return number;
     }
 
+    public String getObject()
+    {
+        return text;
+    }
+
 }
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ParserUtilities.java b/common/source/java/ch/systemsx/cisd/common/parser/ParserUtilities.java
index 6721c422f3f..0778dd90ee4 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/ParserUtilities.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ParserUtilities.java
@@ -59,6 +59,7 @@ public final class ParserUtilities
         {
             this(content, null);
         }
+
         public LineSplitter(final String content, final ILineFilter lineFilterOrNull)
         {
             assert content != null : "Unspecified context.";
@@ -72,7 +73,7 @@ public final class ParserUtilities
         {
             this(file, null);
         }
-        
+
         public LineSplitter(final File file, final ILineFilter lineFilterOrNull)
                 throws IOExceptionUnchecked
         {
@@ -93,7 +94,7 @@ public final class ParserUtilities
             return (lineFilterOrNull == null) ? AlwaysAcceptLineFilter.INSTANCE : lineFilterOrNull;
         }
 
-       public void close()
+        public void close()
         {
             lineIterator.close();
         }
@@ -101,15 +102,16 @@ public final class ParserUtilities
         /**
          * Returns the next line that is accepted by the <var>lineFilter</var>.
          */
-        public Line tryNextLine()
+        public ILine<String> tryNextLine()
         {
             for (int line = lineNumber; lineIterator.hasNext(); line++)
             {
                 final String nextLine = lineIterator.nextLine();
-                if (lineFilter.acceptLine(nextLine, line))
+                final Line ret = new Line(line, nextLine);
+                if (lineFilter.acceptLine(ret))
                 {
                     lineNumber = line + 1;
-                    return new Line(line, nextLine);
+                    return ret;
                 }
             }
             return null;
@@ -128,7 +130,7 @@ public final class ParserUtilities
      * @param content the content that is going to be analyzed. Can not be <code>null</code>.
      * @return <code>null</code> if all lines have been filtered out.
      */
-    public final static Line tryGetFirstAcceptedLine(final String content,
+    public final static ILine<String> tryGetFirstAcceptedLine(final String content,
             final ILineFilter lineFilterOrNull)
     {
         final LineSplitter splitter = new LineSplitter(content, lineFilterOrNull);
@@ -154,7 +156,7 @@ public final class ParserUtilities
      *            exists.
      * @return <code>null</code> if all lines have been filtered out.
      */
-    public final static Line tryGetFirstAcceptedLine(final File file,
+    public final static ILine<String> tryGetFirstAcceptedLine(final File file,
             final ILineFilter lineFilterOrNull)
     {
         final LineSplitter splitter = new LineSplitter(file, lineFilterOrNull);
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java b/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
index c47a095b455..4dea725e2cd 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
@@ -184,7 +184,7 @@ public class TabFileLoader<T>
     {
         assert reader != null : "Unspecified reader";
 
-        final Iterator<Line> lineIterator = createLineIterator(reader);
+        final Iterator<ILine<String>> lineIterator = createLineIterator(reader);
         return iterate(lineIterator, defaults);
     }
 
@@ -201,7 +201,7 @@ public class TabFileLoader<T>
     {
         assert reader != null : "Unspecified reader";
 
-        final Iterator<Line> lineIterator = createLineIterator(reader);
+        final Iterator<ILine<String>> lineIterator = createLineIterator(reader);
         return load(lineIterator, defaults);
     }
 
@@ -220,7 +220,7 @@ public class TabFileLoader<T>
 
         try
         {
-            final Iterator<Line> lineIterator = createLineIterator(stream);
+            final Iterator<ILine<String>> lineIterator = createLineIterator(stream);
             return iterate(lineIterator, defaults);
         } catch (IOException ex)
         {
@@ -243,7 +243,7 @@ public class TabFileLoader<T>
 
         try
         {
-            final Iterator<Line> lineIterator = createLineIterator(stream);
+            final Iterator<ILine<String>> lineIterator = createLineIterator(stream);
             return load(lineIterator, defaults);
         } catch (IOException ex)
         {
@@ -253,17 +253,17 @@ public class TabFileLoader<T>
 
     public static final Map<String, String> parseDefaults(Reader reader)
     {
-        final Iterator<Line> lineIterator = createLineIterator(reader);
+        final Iterator<ILine<String>> lineIterator = createLineIterator(reader);
         final Map<String, String> defaults = new HashMap<String, String>();
         return parseDefaults(lineIterator, defaults);
     }
 
-    private static final Map<String, String> parseDefaults(final Iterator<Line> lineIterator,
-            final Map<String, String> defaults)
+    private static final Map<String, String> parseDefaults(
+            final Iterator<ILine<String>> lineIterator, final Map<String, String> defaults)
     {
         while (lineIterator.hasNext())
         {
-            Line line = lineIterator.next();
+            ILine<String> line = lineIterator.next();
             String text = line.getText();
             if (DEFAULT_SECTION.equals(text))
             {
@@ -281,10 +281,11 @@ public class TabFileLoader<T>
         return defaults;
     }
 
-    private List<T> load(final Iterator<Line> lineIterator, Map<String, String> fileDefaults)
+    private List<T> load(final Iterator<ILine<String>> lineIterator,
+            Map<String, String> fileDefaults)
     {
-        Line previousLine = null;
-        Line line = null;
+        ILine<String> previousLine = null;
+        ILine<String> line = null;
         boolean previousLineHasColumnHeaders = false;
 
         Map<String, String> defaults = new HashMap<String, String>(fileDefaults);
@@ -321,7 +322,7 @@ public class TabFileLoader<T>
             headerLine = line.getText();
         }
 
-        final IParser<T> parser = createParser();
+        final IParser<T, String> parser = createParser();
         final String[] tokens = StringUtils.split(headerLine, TOKENS_SEPARATOR);
         int lastEmptyHeadersToSkip = countLastEmptyTokens(headerLine);
         final int headerLength = tokens.length;
@@ -330,17 +331,18 @@ public class TabFileLoader<T>
         final IPropertyMapper propertyMapper = new DefaultPropertyMapper(tokens, defaults);
         parser.setObjectFactory(factory.createFactory(propertyMapper));
 
-        Line firstContentLine = previousLineHasColumnHeaders ? line : null;
-        Iterator<Line> contentLineIterator =
+        ILine<String> firstContentLine = previousLineHasColumnHeaders ? line : null;
+        Iterator<ILine<String>> contentLineIterator =
                 createContentIterator(firstContentLine, lineIterator, lastEmptyHeadersToSkip);
         final ILineFilter filter = AlwaysAcceptLineFilter.INSTANCE;
         return parser.parse(contentLineIterator, filter, headerLength);
     }
 
-    private Iterator<T> iterate(final Iterator<Line> lineIterator, Map<String, String> fileDefaults)
+    private Iterator<T> iterate(final Iterator<ILine<String>> lineIterator,
+            Map<String, String> fileDefaults)
     {
-        Line previousLine = null;
-        Line line = null;
+        ILine<String> previousLine = null;
+        ILine<String> line = null;
         boolean previousLineHasColumnHeaders = false;
 
         Map<String, String> defaults = new HashMap<String, String>(fileDefaults);
@@ -393,7 +395,7 @@ public class TabFileLoader<T>
             headerLine = line.getText();
         }
 
-        final IParser<T> parser = createParser();
+        final IParser<T, String> parser = createParser();
         final String[] tokens = StringUtils.split(headerLine, TOKENS_SEPARATOR);
         int lastEmptyHeadersToSkip = countLastEmptyTokens(headerLine);
         final int headerLength = tokens.length;
@@ -402,26 +404,26 @@ public class TabFileLoader<T>
         final IPropertyMapper propertyMapper = new DefaultPropertyMapper(tokens, defaults);
         parser.setObjectFactory(factory.createFactory(propertyMapper));
 
-        Line firstContentLine = previousLineHasColumnHeaders ? line : null;
-        Iterator<Line> contentLineIterator =
+        ILine<String> firstContentLine = previousLineHasColumnHeaders ? line : null;
+        Iterator<ILine<String>> contentLineIterator =
                 createContentIterator(firstContentLine, lineIterator, lastEmptyHeadersToSkip);
         final ILineFilter filter = AlwaysAcceptLineFilter.INSTANCE;
         return parser.parseIteratively(contentLineIterator, filter, headerLength);
     }
 
-    private static boolean startsDefaultSection(Line line)
+    private static boolean startsDefaultSection(ILine<String> line)
     {
         String text = line.getText();
         return DEFAULT_SECTION.equals(text);
     }
 
-    private static boolean startsWithComment(Line line)
+    private static boolean startsWithComment(ILine<String> line)
     {
         String text = line.getText();
         return COMMENT_REGEXP_PATTERN.matcher(text).matches();
     }
 
-    private static String trimComment(Line previousLine)
+    private static String trimComment(ILine<String> previousLine)
     {
         String text = previousLine.getText().trim();
         if (text.startsWith(COMMENT_PREFIX))
@@ -433,7 +435,7 @@ public class TabFileLoader<T>
         }
     }
 
-    private static boolean isComment(Line line)
+    private static boolean isComment(ILine<String> line)
     {
         String text = line.getText();
         return COMMENT_PREFIX.equals(text);
@@ -445,26 +447,27 @@ public class TabFileLoader<T>
      * @param lastEmptyTokensToSkip the number of token separators which will be removed form the
      *            end of each iterated line
      */
-    private static Iterator<Line> createContentIterator(final Line firstContentLineOrNull,
-            final Iterator<Line> lineIterator, final int lastEmptyTokensToSkip)
+    private static Iterator<ILine<String>> createContentIterator(
+            final ILine<String> firstContentLineOrNull, final Iterator<ILine<String>> lineIterator,
+            final int lastEmptyTokensToSkip)
     {
 
         final String suffixToDelete = multiply(lastEmptyTokensToSkip, TOKENS_SEPARATOR);
-        return new Iterator<Line>()
+        return new Iterator<ILine<String>>()
             {
-                private Line firstLineOrNull = firstContentLineOrNull;
+                private ILine<String> firstLineOrNull = firstContentLineOrNull;
 
                 public boolean hasNext()
                 {
                     return firstLineOrNull != null || lineIterator.hasNext();
                 }
 
-                public Line next()
+                public ILine<String> next()
                 {
                     return trim(nextUntrimmed());
                 }
 
-                private Line trim(Line line)
+                private ILine<String> trim(ILine<String> line)
                 {
                     if (lastEmptyTokensToSkip == 0)
                     {
@@ -496,11 +499,11 @@ public class TabFileLoader<T>
                     }
                 }
 
-                private Line nextUntrimmed()
+                private ILine<String> nextUntrimmed()
                 {
                     if (firstLineOrNull != null)
                     {
-                        Line line = firstLineOrNull;
+                        ILine<String> line = firstLineOrNull;
                         firstLineOrNull = null;
                         return line;
                     } else
@@ -539,18 +542,19 @@ public class TabFileLoader<T>
         return counter;
     }
 
-    private static Iterator<Line> createLineIterator(final Reader reader)
+    private static Iterator<ILine<String>> createLineIterator(final Reader reader)
     {
         final LineIterator lineIterator = IOUtils.lineIterator(reader);
-        final Iterator<Line> iterator = new TabFileLineIterator(lineIterator);
+        final Iterator<ILine<String>> iterator = new TabFileLineIterator(lineIterator);
         return iterator;
     }
 
-    private static Iterator<Line> createLineIterator(final InputStream stream) throws IOException
+    private static Iterator<ILine<String>> createLineIterator(final InputStream stream)
+            throws IOException
     {
         final LineIterator lineIterator =
                 IOUtils.lineIterator(stream, UnicodeUtils.DEFAULT_UNICODE_CHARSET);
-        final Iterator<Line> iterator = new TabFileLineIterator(lineIterator);
+        final Iterator<ILine<String>> iterator = new TabFileLineIterator(lineIterator);
         return iterator;
     }
 
@@ -577,19 +581,19 @@ public class TabFileLoader<T>
         }
     }
 
-    private final <E> IParser<E> createParser()
+    private final <E> IParser<E, String> createParser()
     {
         DefaultLineTokenizer tokenizer = new DefaultLineTokenizer();
         // recognize default Excel text qualifiers
         tokenizer.setProperty(PropertyKey.QUOTE_CHARS, "'\"");
-        return new DefaultParser<E>(tokenizer);
+        return new DefaultParser<E, String>(tokenizer);
     }
 
     //
     // Helper classes
     //
 
-    private final static class TabFileLineIterator implements Iterator<Line>
+    private final static class TabFileLineIterator implements Iterator<ILine<String>>
     {
 
         private final LineIterator lineIterator;
@@ -610,7 +614,7 @@ public class TabFileLoader<T>
             lineIterator.remove();
         }
 
-        public final Line next()
+        public final ILine<String> next()
         {
             return new Line(++lineNumber, lineIterator.nextLine());
         }
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/filter/AlwaysAcceptLineFilter.java b/common/source/java/ch/systemsx/cisd/common/parser/filter/AlwaysAcceptLineFilter.java
index 6b9a72647b1..309684912c6 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/filter/AlwaysAcceptLineFilter.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/filter/AlwaysAcceptLineFilter.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.common.parser.filter;
 
+import ch.systemsx.cisd.common.parser.ILine;
+
 /**
  * A default line filter that accepts any line.
  * 
@@ -33,7 +35,7 @@ public final class AlwaysAcceptLineFilter implements ILineFilter
     // ILineFilter
     //
 
-    public final boolean acceptLine(final String line, final int lineNumber)
+    public final <T> boolean acceptLine(ILine<T> line)
     {
         return true;
     }
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/filter/ExcludeEmptyAndCommentLineFilter.java b/common/source/java/ch/systemsx/cisd/common/parser/filter/ExcludeEmptyAndCommentLineFilter.java
index 91b12e2262f..3b029bbd75c 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/filter/ExcludeEmptyAndCommentLineFilter.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/filter/ExcludeEmptyAndCommentLineFilter.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.common.parser.filter;
 
+import ch.systemsx.cisd.common.parser.ILine;
+
 /**
  * A default <code>LineFilter</code> implementation that excludes empty and comment lines.
  * <p>
@@ -36,10 +38,10 @@ public final class ExcludeEmptyAndCommentLineFilter implements ILineFilter
     // ILineFilter
     //
 
-    public final boolean acceptLine(final String line, final int lineNumber)
+    public final <T> boolean acceptLine(ILine<T> line)
     {
         assert line != null : "Unspecified line";
-        final String trimmed = line.trim();
+        final String trimmed = line.getText().trim();
         return trimmed.length() > 0 && trimmed.startsWith("#") == false;
     }
 }
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/filter/ILineFilter.java b/common/source/java/ch/systemsx/cisd/common/parser/filter/ILineFilter.java
index 5c2e5cad8ff..7f66d1afda0 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/filter/ILineFilter.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/filter/ILineFilter.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.common.parser.filter;
 
+import ch.systemsx.cisd.common.parser.ILine;
+
 /**
  * A line filter for <code>ReaderParser</code>.
  * 
@@ -29,5 +31,5 @@ public interface ILineFilter
      * 
      * @param line the line read from the <code>Reader</code>. Can not be <code>null</code>.
      */
-    public boolean acceptLine(final String line, final int lineNumber);
+    public <T> boolean acceptLine(final ILine<T> line);
 }
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/filter/NonEmptyLineFilter.java b/common/source/java/ch/systemsx/cisd/common/parser/filter/NonEmptyLineFilter.java
index 431073d753c..969b005824b 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/filter/NonEmptyLineFilter.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/filter/NonEmptyLineFilter.java
@@ -17,6 +17,8 @@ package ch.systemsx.cisd.common.parser.filter;
 
 import org.apache.commons.lang.StringUtils;
 
+import ch.systemsx.cisd.common.parser.ILine;
+
 /**
  * A default <code>LineFilter</code> implementation that excludes empty lines.
  * 
@@ -34,8 +36,8 @@ public final class NonEmptyLineFilter implements ILineFilter
     // ILineFilter
     //
 
-    public final boolean acceptLine(final String line, final int lineNumber)
+    public final <T> boolean acceptLine(ILine<T> line)
     {
-        return StringUtils.isNotBlank(line);
+        return StringUtils.isNotBlank(line.getText());
     }
 }
\ No newline at end of file
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/parser/DefaultParserTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/parser/DefaultParserTest.java
index 8b083ef7993..4b0b19824a9 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/parser/DefaultParserTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/parser/DefaultParserTest.java
@@ -16,7 +16,10 @@
 
 package ch.systemsx.cisd.common.parser;
 
-import static org.testng.AssertJUnit.*;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.fail;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -31,25 +34,24 @@ import org.testng.annotations.Test;
  */
 public final class DefaultParserTest
 {
-    private final static List<String> text =
-            Arrays.asList("", "# This is a comment", "firstName\tlastName\taddress\tcity",
-                    "Charles\tDarwin\tHumboldt Ave. 1865\t4242 Somewhere",
-                    "Albert\tEinstein\tNewton Road 1905\t4711 Princton");
+    private final static List<String> text = Arrays.asList("", "# This is a comment",
+            "firstName\tlastName\taddress\tcity",
+            "Charles\tDarwin\tHumboldt Ave. 1865\t4242 Somewhere",
+            "Albert\tEinstein\tNewton Road 1905\t4711 Princton");
 
-    private final static List<String> textWithTab =
-            Arrays.asList("", "# This is a comment", "firstName\tlastName\taddress\tcity",
-                    "Charles\tDarwin\tHumboldt Ave. 1865\t4242 Somewhere",
-                    "Albert\t\tNewton Road 1905\t");
+    private final static List<String> textWithTab = Arrays.asList("", "# This is a comment",
+            "firstName\tlastName\taddress\tcity",
+            "Charles\tDarwin\tHumboldt Ave. 1865\t4242 Somewhere", "Albert\t\tNewton Road 1905\t");
 
-    private final static List<String> textWithMissingLastCells =
-            Arrays.asList("", "# This is a comment", "firstName\tlastName\taddress\tcity",
-                    "\tDarwin\tHumboldt Ave. 1865", "Albert\tEinstein");
+    private final static List<String> textWithMissingLastCells = Arrays.asList("",
+            "# This is a comment", "firstName\tlastName\taddress\tcity",
+            "\tDarwin\tHumboldt Ave. 1865", "Albert\tEinstein");
 
     private final static int HEADER_LENGTH = 4;
 
-    private final static IParser<String[]> createParser()
+    private final static IParser<String[], String> createParser()
     {
-        final IParser<String[]> parser = new DefaultParser<String[]>();
+        final IParser<String[], String> parser = DefaultParser.createDefaultParser();
         parser.setObjectFactory(IParserObjectFactory.STRING_ARRAY_OBJECT_FACTORY);
         return parser;
     }
@@ -57,7 +59,7 @@ public final class DefaultParserTest
     @Test
     public final void testParseWithoutFactoryAndHeader()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         final List<String[]> result =
                 parser.parse(createLineIterator(text), new HeaderLineFilter(), HEADER_LENGTH);
         assertEquals(3, result.size());
@@ -70,7 +72,7 @@ public final class DefaultParserTest
     @Test
     public final void testParseIterativelyWithoutFactoryAndHeader()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         final Iterator<String[]> result =
                 parser.parseIteratively(createLineIterator(text), new HeaderLineFilter(),
                         HEADER_LENGTH);
@@ -89,7 +91,7 @@ public final class DefaultParserTest
     @Test
     public final void testParseWithoutFactoryWithLineFilter()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         final List<String[]> result =
                 parser.parse(createLineIterator(text), new HeaderLineFilter(3), HEADER_LENGTH);
         assertEquals(2, result.size());
@@ -100,11 +102,9 @@ public final class DefaultParserTest
     @Test
     public final void testParseFileWithTabs()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         final List<String[]> result =
-                parser
-                        .parse(createLineIterator(textWithTab), new HeaderLineFilter(),
-                                HEADER_LENGTH);
+                parser.parse(createLineIterator(textWithTab), new HeaderLineFilter(), HEADER_LENGTH);
         assertEquals(3, result.size());
         assertEquals("Albert", result.get(2)[0]);
         assertEquals("", result.get(2)[1]);
@@ -115,7 +115,7 @@ public final class DefaultParserTest
     @Test
     public final void testParseFileWithMissingLastCells()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         final List<String[]> result =
                 parser.parse(createLineIterator(textWithMissingLastCells), new HeaderLineFilter(),
                         HEADER_LENGTH);
@@ -133,7 +133,7 @@ public final class DefaultParserTest
     @Test
     public final void testParseWithColumnSizeMismatching()
     {
-        final IParser<String[]> parser = createParser();
+        final IParser<String[], String> parser = createParser();
         try
         {
             parser.parse(createLineIterator(text), new HeaderLineFilter(3), HEADER_LENGTH + 1);
@@ -149,18 +149,20 @@ public final class DefaultParserTest
     @Test
     public final void testCreateObjectWithParserException()
     {
-        final IParser<String[]> parser = new DefaultParser<String[]>()
-            {
-                //
-                // DefaultReaderParser
-                //
-
-                @Override
-                protected final String[] createObject(final String[] tokens) throws ParserException
-                {
-                    throw new ParserException("");
-                }
-            };
+        final IParser<String[], String> parser =
+                new DefaultParser<String[], String>(new DefaultLineTokenizer())
+                    {
+                        //
+                        // DefaultReaderParser
+                        //
+
+                        @Override
+                        protected final String[] createObject(final String[] tokens)
+                                throws ParserException
+                        {
+                            throw new ParserException("");
+                        }
+                    };
         parser.setObjectFactory(IParserObjectFactory.STRING_ARRAY_OBJECT_FACTORY);
         try
         {
@@ -175,9 +177,9 @@ public final class DefaultParserTest
         }
     }
 
-    private Iterator<Line> createLineIterator(final List<String> lines)
+    private Iterator<ILine<String>> createLineIterator(final List<String> lines)
     {
-        return new Iterator<Line>()
+        return new Iterator<ILine<String>>()
             {
                 private final Iterator<String> iterator = lines.iterator();
 
@@ -188,7 +190,7 @@ public final class DefaultParserTest
                     throw new UnsupportedOperationException();
                 }
 
-                public Line next()
+                public ILine<String> next()
                 {
                     return new Line(++lineNumber, iterator.next());
                 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/parser/HeaderLineFilterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/parser/HeaderLineFilterTest.java
index dd5103f7b36..7c4eb1280ae 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/parser/HeaderLineFilterTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/parser/HeaderLineFilterTest.java
@@ -33,7 +33,7 @@ public class HeaderLineFilterTest
         final HeaderLineFilter filter = new HeaderLineFilter();
         for (int i = 0; i < 4; ++i)
         {
-            assert filter.acceptLine("text", i) : "Line " + i;
+            assert filter.acceptLine(new Line(i, "text")) : "Line " + i;
         }
     }
 
@@ -56,12 +56,12 @@ public class HeaderLineFilterTest
         {
             if (headerLine == i)
             {
-                assert filter.acceptLine("text", i) == false : "Line " + i + " (header line: "
-                        + headerLine + ")";
+                assert filter.acceptLine(new Line(i, "text")) == false : "Line " + i
+                        + " (header line: " + headerLine + ")";
             } else
             {
-                assert filter.acceptLine("text", i) : "Line " + i + " (header line: " + headerLine
-                        + ")";
+                assert filter.acceptLine(new Line(i, "text")) : "Line " + i + " (header line: "
+                        + headerLine + ")";
             }
         }
     }
@@ -70,36 +70,36 @@ public class HeaderLineFilterTest
     public void testEmptyHeaderLine()
     {
         final HeaderLineFilter filter = new HeaderLineFilter(0);
-        assert filter.acceptLine("", 0) == false;
-        assert filter.acceptLine("something", 1);
+        assert filter.acceptLine(new Line(0, "")) == false;
+        assert filter.acceptLine(new Line(1, "something"));
     }
 
     @Test
     public void testSkipEmptyLine()
     {
         final HeaderLineFilter filter = new HeaderLineFilter();
-        assert filter.acceptLine("", 0) == false;
+        assert filter.acceptLine(new Line(0, "")) == false;
     }
 
     @Test
     public void testSkipBlankLine()
     {
         final HeaderLineFilter filter = new HeaderLineFilter();
-        assert filter.acceptLine("  ", 0) == false;
+        assert filter.acceptLine(new Line(0, "  ")) == false;
     }
 
     @Test
     public void testSkipCommentLine()
     {
         final HeaderLineFilter filter = new HeaderLineFilter();
-        assert filter.acceptLine("# Some comment", 0) == false;
+        assert filter.acceptLine(new Line(0, "# Some comment")) == false;
     }
 
     @Test
     public void testSkipCommentLineWithLeadingWhiteSpace()
     {
         final HeaderLineFilter filter = new HeaderLineFilter();
-        assert filter.acceptLine("\t# Some comment", 0) == false;
+        assert filter.acceptLine(new Line(0, "\t# Some comment")) == false;
     }
 
 }
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/parser/ParserUtilitiesTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/parser/ParserUtilitiesTest.java
index d4f948377f0..461ea7cefa2 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/parser/ParserUtilitiesTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/parser/ParserUtilitiesTest.java
@@ -43,11 +43,11 @@ import ch.systemsx.cisd.common.parser.filter.NonEmptyLineFilter;
 public final class ParserUtilitiesTest
 {
 
-    private static final File unitTestRootDirectory =
-            new File("targets" + File.separator + "unit-test-wd");
+    private static final File unitTestRootDirectory = new File("targets" + File.separator
+            + "unit-test-wd");
 
-    private static final File workingDirectory =
-            new File(unitTestRootDirectory, "ParserUtilitiesTest");
+    private static final File workingDirectory = new File(unitTestRootDirectory,
+            "ParserUtilitiesTest");
 
     @BeforeClass
     public final void setUp()
@@ -97,7 +97,7 @@ public final class ParserUtilitiesTest
             { StringUtils.EMPTY, "non-empty line", StringUtils.EMPTY, "hello" };
         File file = new File(workingDirectory, "test.txt");
         FileUtils.writeLines(file, Arrays.asList(lines));
-        Line line = ParserUtilities.tryGetFirstAcceptedLine(file, null);
+        ILine<String> line = ParserUtilities.tryGetFirstAcceptedLine(file, null);
         assertEquals(StringUtils.EMPTY, line.getText());
         assertEquals(0, line.getNumber());
         assert file.delete();
@@ -112,7 +112,7 @@ public final class ParserUtilitiesTest
         FileUtils.writeLines(file, Arrays.asList(lines));
         ParserUtilities.LineSplitter splitter =
                 new ParserUtilities.LineSplitter(file, NonEmptyLineFilter.INSTANCE);
-        Line line = splitter.tryNextLine();
+        ILine<String> line = splitter.tryNextLine();
         assertEquals("non-empty line", line.getText());
         assertEquals(1, line.getNumber());
         line = splitter.tryNextLine();
@@ -129,7 +129,7 @@ public final class ParserUtilitiesTest
             { StringUtils.EMPTY, "# comment line", StringUtils.EMPTY, "hello" };
         File file = new File(workingDirectory, "test.txt");
         FileUtils.writeLines(file, Arrays.asList(lines));
-        Line line =
+        ILine<String> line =
                 ParserUtilities.tryGetFirstAcceptedLine(file,
                         ExcludeEmptyAndCommentLineFilter.INSTANCE);
         assertEquals("hello", line.getText());
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/java/MigrationStepExecutor.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/java/MigrationStepExecutor.java
index c86915657ca..97445fe63d0 100644
--- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/java/MigrationStepExecutor.java
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/java/MigrationStepExecutor.java
@@ -24,7 +24,7 @@ import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
 import ch.systemsx.cisd.common.Script;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
-import ch.systemsx.cisd.common.parser.Line;
+import ch.systemsx.cisd.common.parser.ILine;
 import ch.systemsx.cisd.common.parser.ParserUtilities;
 import ch.systemsx.cisd.common.parser.filter.ILineFilter;
 import ch.systemsx.cisd.common.utilities.ClassUtils;
@@ -79,13 +79,15 @@ public class MigrationStepExecutor extends SimpleJdbcDaoSupport implements IMigr
         final ParserUtilities.LineSplitter splitter =
                 new ParserUtilities.LineSplitter(content, new ILineFilter()
                     {
-                        public boolean acceptLine(String line, int lineNumber)
+                        public <T> boolean acceptLine(ILine<T> line)
                         {
-                            return StringUtils.isNotBlank(line) && line.startsWith("--");
+                            String text = line.getText();
+                            return StringUtils.isNotBlank(text) && text.startsWith("--");
                         }
+
                     });
         IMigrationStep stepOrNull = null;
-        Line lineOrNull;
+        ILine<?> lineOrNull;
         while (stepOrNull == null && (lineOrNull = splitter.tryNextLine()) != null)
         {
             stepOrNull = tryExtractMigrationStepFromLine(lineOrNull.getText());
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/BisExcelFileLoader.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/BisExcelFileLoader.java
new file mode 100644
index 00000000000..56556fce0f7
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/BisExcelFileLoader.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.parser;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.ss.usermodel.Sheet;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.io.NamedReader;
+import ch.systemsx.cisd.common.parser.ExcelFileLoader;
+import ch.systemsx.cisd.common.parser.IParserObjectFactoryFactory;
+import ch.systemsx.cisd.common.parser.ParserException;
+import ch.systemsx.cisd.common.parser.ParsingException;
+import ch.systemsx.cisd.common.parser.TabFileLoader;
+
+/**
+ * A <i>openBIS</i> {@link TabFileLoader} extension which translates a {@link ParsingException} into
+ * a more user friendly {@link UserFailureException}.
+ * <p>
+ * Note that this extension prefers to work with {@link NamedReader}.
+ * </p>
+ * 
+ * @author Christian Ribeaud
+ */
+public final class BisExcelFileLoader<T> extends ExcelFileLoader<T>
+{
+
+    private static final String MESSAGE_FORMAT =
+            "A problem has occurred while parsing line %d of file '%s':\n  %s";
+
+    private static final String ERROR_IN_FILE_MESSAGE_FORMAT =
+            "A problem has occurred while parsing file '%s':\n  %s";
+
+    private final boolean acceptEmptyFiles;
+
+    public BisExcelFileLoader(final IParserObjectFactoryFactory<T> factory, boolean acceptEmptyFiles)
+    {
+        super(factory);
+        this.acceptEmptyFiles = acceptEmptyFiles;
+    }
+
+    private final static void translateParsingException(final ParsingException parsingException,
+            final String sectionName)
+    {
+        final RuntimeException causeException = parsingException.getCauseRuntimeException();
+        final String message =
+                causeException == null ? parsingException.getMessage() : causeException
+                        .getMessage();
+        throw UserFailureException.fromTemplate(MESSAGE_FORMAT, parsingException.getLineNumber(),
+                sectionName, message);
+    }
+
+    private final static void translateParserException(final ParserException ex,
+            final String sectionName)
+    {
+        final String message = ex.getMessage();
+        throw UserFailureException.fromTemplate(ERROR_IN_FILE_MESSAGE_FORMAT, sectionName, message);
+    }
+
+    //
+    // TabFileLoader
+    //
+    public List<T> load(final Sheet sheet, int begin, int end, String sectionName,
+            final Map<String, String> defaults) throws ParserException, ParsingException,
+            IllegalArgumentException
+    {
+        assert sheet != null : "Excel Sheet cannot be null.";
+        try
+        {
+            final List<T> list = super.load(sheet, begin, end, defaults);
+            if (acceptEmptyFiles == false && list.size() == 0)
+            {
+                throw new UserFailureException("Given file '" + sectionName
+                        + "' is empty or does not contain any meaningful information.");
+            }
+            return list;
+        } catch (final IllegalArgumentException illegalArgumentException)
+        {
+            throw new UserFailureException(illegalArgumentException.getMessage(),
+                    illegalArgumentException);
+        } catch (final ParsingException e)
+        {
+            translateParsingException(e, sectionName);
+            // Never used.
+            return null;
+        } catch (final ParserException e)
+        {
+            translateParserException(e, sectionName);
+            // Never used.
+            return null;
+        }
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/ExcelFileSection.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/ExcelFileSection.java
new file mode 100644
index 00000000000..dd4a7c33c0b
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/ExcelFileSection.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+
+import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class ExcelFileSection
+{
+    private static final String SECTION_FILE_DEFAULT = "DEFAULT";
+
+    private final Sheet sheet;
+
+    private final int begin;
+
+    private final int end;
+
+    private final String sectionName;
+
+    private ExcelFileSection(Sheet sheet, String sectionName, int begin, int end)
+    {
+        assert sheet != null;
+        this.sectionName = sectionName;
+        this.sheet = sheet;
+        this.begin = begin;
+        this.end = end;
+    }
+
+    public String getSectionName()
+    {
+        return sectionName;
+    }
+
+    public Sheet getSheet()
+    {
+        return sheet;
+    }
+
+    public int getBegin()
+    {
+        return begin;
+    }
+
+    public int getEnd()
+    {
+        return end;
+    }
+
+    public static ExcelFileSection createFromInputStream(InputStream stream, String sectionName)
+    {
+        try
+        {
+            POIFSFileSystem poifsFileSystem = new POIFSFileSystem(stream);
+            Workbook wb = new HSSFWorkbook(poifsFileSystem);
+            Sheet sheet = wb.getSheetAt(0);
+            return new ExcelFileSection(sheet, sectionName, 0, sheet.getLastRowNum());
+        } catch (IOException e)
+        {
+            throw new IOExceptionUnchecked(e);
+        }
+    }
+
+    public static List<ExcelFileSection> extractSections(InputStream stream)
+    {
+        List<ExcelFileSection> sections = new ArrayList<ExcelFileSection>();
+        try
+        {
+            POIFSFileSystem poifsFileSystem = new POIFSFileSystem(stream);
+            Workbook wb = new HSSFWorkbook(poifsFileSystem);
+            Sheet sheet = wb.getSheetAt(0);
+            String sectionName = null;
+            Integer begin = null;
+            for (Row row : sheet)
+            {
+                String newSectionName = tryGetSectionName(row);
+                if (newSectionName != null)
+                {
+                    if (sectionName != null && begin != null)
+                    {
+                        if (sectionName.equals(newSectionName))
+                        {
+                            continue;
+                        }
+                        if (newSectionName.equals(SECTION_FILE_DEFAULT))
+                        {
+                            continue;
+                        } else
+                        {
+                            sections.add(new ExcelFileSection(sheet, sectionName, begin, row
+                                    .getRowNum()));
+                            sectionName = newSectionName;
+                            begin = row.getRowNum() + 1;
+                        }
+                    } else
+                    {
+                        sectionName = newSectionName;
+                        begin = row.getRowNum() + 1;
+                    }
+                } else if (sectionName == null || begin == null)
+                {
+                    throw new UserFailureException("Discovered the unnamed section in the file");
+                }
+                if (row.getRowNum() == sheet.getLastRowNum())
+                {
+                    sections.add(new ExcelFileSection(sheet, sectionName, begin, row.getRowNum()));
+                }
+            }
+        } catch (IOException e)
+        {
+            throw new IOExceptionUnchecked(e);
+        } finally
+        {
+            IOUtils.closeQuietly(stream);
+        }
+        return sections;
+    }
+
+    private static String tryGetSectionName(Row row)
+    {
+        final String beginSection = "[";
+        final String endSection = "]";
+        if (row == null || row.getCell(0) == null
+                || row.getCell(0).getCellType() != Cell.CELL_TYPE_STRING)
+        {
+            return null;
+        }
+        String trimmedCell = row.getCell(0).getStringCellValue();
+
+        if (trimmedCell.startsWith(beginSection) && trimmedCell.endsWith(endSection))
+        {
+            return trimmedCell.substring(1, trimmedCell.length() - 1);
+        } else
+        {
+            return null;
+        }
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/SampleUploadSectionsParser.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/SampleUploadSectionsParser.java
index e343d31ed4b..a7ca423d9e4 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/SampleUploadSectionsParser.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/parser/SampleUploadSectionsParser.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import ch.systemsx.cisd.common.io.DelegatedReader;
+import ch.systemsx.cisd.common.parser.ExcelFileLoader;
 import ch.systemsx.cisd.common.parser.IParserObjectFactory;
 import ch.systemsx.cisd.common.parser.IParserObjectFactoryFactory;
 import ch.systemsx.cisd.common.parser.IPropertyMapper;
@@ -153,6 +154,34 @@ public class SampleUploadSectionsParser
         return tabFileLoader;
     }
 
+    private static BisExcelFileLoader<NewSample> createSampleLoaderFromExcel(
+            final SampleType sampleType, final boolean isAutoGenerateCodes,
+            final boolean allowExperiments, final BatchOperationKind operationKind)
+    {
+        final BisExcelFileLoader<NewSample> tabFileLoader =
+                new BisExcelFileLoader<NewSample>(new IParserObjectFactoryFactory<NewSample>()
+                    {
+                        public final IParserObjectFactory<NewSample> createFactory(
+                                final IPropertyMapper propertyMapper) throws ParserException
+                        {
+                            switch (operationKind)
+                            {
+                                case REGISTRATION:
+                                    return new NewSampleParserObjectFactory(sampleType,
+                                            propertyMapper, isAutoGenerateCodes == false,
+                                            allowExperiments);
+                                case UPDATE:
+                                    return new UpdatedSampleParserObjectFactory(sampleType,
+                                            propertyMapper, isAutoGenerateCodes == false,
+                                            allowExperiments);
+                            }
+                            throw new UnsupportedOperationException(operationKind
+                                    + " is not supported");
+                        }
+                    }, true);
+        return tabFileLoader;
+    }
+
     private static List<BatchRegistrationResult> loadSamplesFromFiles(
             Collection<NamedInputStream> uploadedFiles, SampleType sampleType,
             boolean isAutoGenerateCodes, final List<NewSamplesWithTypes> newSamples,
@@ -162,50 +191,98 @@ public class SampleUploadSectionsParser
                 new ArrayList<BatchRegistrationResult>(uploadedFiles.size());
         for (final NamedInputStream multipartFile : uploadedFiles)
         {
-            List<FileSection> sampleSections = new ArrayList<FileSection>();
-            if (sampleType.isDefinedInFileEntityTypeCode())
+            if (multipartFile.getOriginalFilename().toLowerCase().endsWith("xls"))
             {
-                sampleSections
-                        .addAll(FileSection.extractSections(multipartFile.getUnicodeReader()));
+                List<ExcelFileSection> sampleSections = new ArrayList<ExcelFileSection>();
+                if (sampleType.isDefinedInFileEntityTypeCode())
+                {
+                    sampleSections.addAll(ExcelFileSection.extractSections(multipartFile
+                            .getInputStream()));
+                } else
+                {
+                    sampleSections.add(ExcelFileSection.createFromInputStream(
+                            multipartFile.getInputStream(), sampleType.getCode()));
+                }
+                int sampleCounter = 0;
+                Map<String, String> defaults = Collections.emptyMap();
+                for (ExcelFileSection fs : sampleSections)
+                {
+                    if (fs.getSectionName().equals("DEFAULT"))
+                    {
+                        defaults =
+                                Collections.unmodifiableMap(ExcelFileLoader.parseDefaults(
+                                        fs.getSheet(), fs.getBegin(), fs.getEnd()));
+                    } else
+                    {
+                        SampleType typeFromSection = new SampleType();
+                        typeFromSection.setCode(fs.getSectionName());
+                        final BisExcelFileLoader<NewSample> excelFileLoader =
+                                createSampleLoaderFromExcel(typeFromSection, isAutoGenerateCodes,
+                                        allowExperiments, operationKind);
+                        String sectionInFile =
+                                sampleSections.size() == 1 ? "" : " (section:"
+                                        + fs.getSectionName() + ")";
+                        final List<NewSample> loadedSamples =
+                                excelFileLoader.load(fs.getSheet(), fs.getBegin(), fs.getEnd(),
+                                        multipartFile.getOriginalFilename() + sectionInFile,
+                                        defaults);
+                        if (loadedSamples.size() > 0)
+                        {
+                            newSamples.add(new NewSamplesWithTypes(typeFromSection, loadedSamples));
+                            sampleCounter += loadedSamples.size();
+                        }
+                    }
+                }
+                results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
+                        .format("%s of %d sample(s) is complete.", operationKind.getDescription(),
+                                sampleCounter)));
             } else
             {
-                sampleSections.add(FileSection.createFromInputStream(
-                        multipartFile.getInputStream(), sampleType.getCode()));
-            }
-            int sampleCounter = 0;
-            Map<String, String> defaults = Collections.emptyMap();
-            for (FileSection fs : sampleSections)
-            {
-                if (fs.getSectionName().equals("DEFAULT"))
+                List<FileSection> sampleSections = new ArrayList<FileSection>();
+                if (sampleType.isDefinedInFileEntityTypeCode())
                 {
-                    defaults =
-                            Collections.unmodifiableMap(TabFileLoader.parseDefaults(fs
-                                    .getContentReader()));
+                    sampleSections.addAll(FileSection.extractSections(multipartFile
+                            .getUnicodeReader()));
                 } else
                 {
-                    Reader reader = fs.getContentReader();
-                    SampleType typeFromSection = new SampleType();
-                    typeFromSection.setCode(fs.getSectionName());
-                    final BisTabFileLoader<NewSample> tabFileLoader =
-                            createSampleLoader(typeFromSection, isAutoGenerateCodes,
-                                    allowExperiments, operationKind);
-                    String sectionInFile =
-                            sampleSections.size() == 1 ? "" : " (section:" + fs.getSectionName()
-                                    + ")";
-                    final List<NewSample> loadedSamples =
-                            tabFileLoader.load(
-                                    new DelegatedReader(reader, multipartFile.getOriginalFilename()
-                                            + sectionInFile), defaults);
-                    if (loadedSamples.size() > 0)
+                    sampleSections.add(FileSection.createFromInputStream(
+                            multipartFile.getInputStream(), sampleType.getCode()));
+                }
+                int sampleCounter = 0;
+                Map<String, String> defaults = Collections.emptyMap();
+                for (FileSection fs : sampleSections)
+                {
+                    if (fs.getSectionName().equals("DEFAULT"))
+                    {
+                        defaults =
+                                Collections.unmodifiableMap(TabFileLoader.parseDefaults(fs
+                                        .getContentReader()));
+                    } else
                     {
-                        newSamples.add(new NewSamplesWithTypes(typeFromSection, loadedSamples));
-                        sampleCounter += loadedSamples.size();
+                        Reader reader = fs.getContentReader();
+                        SampleType typeFromSection = new SampleType();
+                        typeFromSection.setCode(fs.getSectionName());
+                        final BisTabFileLoader<NewSample> tabFileLoader =
+                                createSampleLoader(typeFromSection, isAutoGenerateCodes,
+                                        allowExperiments, operationKind);
+                        String sectionInFile =
+                                sampleSections.size() == 1 ? "" : " (section:"
+                                        + fs.getSectionName() + ")";
+                        final List<NewSample> loadedSamples =
+                                tabFileLoader.load(
+                                        new DelegatedReader(reader, multipartFile
+                                                .getOriginalFilename() + sectionInFile), defaults);
+                        if (loadedSamples.size() > 0)
+                        {
+                            newSamples.add(new NewSamplesWithTypes(typeFromSection, loadedSamples));
+                            sampleCounter += loadedSamples.size();
+                        }
                     }
                 }
+                results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
+                        .format("%s of %d sample(s) is complete.", operationKind.getDescription(),
+                                sampleCounter)));
             }
-            results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
-                    .format("%s of %d sample(s) is complete.", operationKind.getDescription(),
-                            sampleCounter)));
         }
         return results;
     }
@@ -249,5 +326,4 @@ public class SampleUploadSectionsParser
             }
         }
     }
-
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/parser/MaterialUploadSectionsParser.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/parser/MaterialUploadSectionsParser.java
index 8c1fdd629d9..678bdbd2152 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/parser/MaterialUploadSectionsParser.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/server/parser/MaterialUploadSectionsParser.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import ch.systemsx.cisd.common.io.DelegatedReader;
+import ch.systemsx.cisd.common.parser.ExcelFileLoader;
 import ch.systemsx.cisd.common.parser.IParserObjectFactory;
 import ch.systemsx.cisd.common.parser.IParserObjectFactoryFactory;
 import ch.systemsx.cisd.common.parser.IPropertyMapper;
@@ -33,7 +34,9 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BatchRegistrationResult
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterial;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewMaterialsWithTypes;
+import ch.systemsx.cisd.openbis.generic.shared.parser.BisExcelFileLoader;
 import ch.systemsx.cisd.openbis.generic.shared.parser.BisTabFileLoader;
+import ch.systemsx.cisd.openbis.generic.shared.parser.ExcelFileSection;
 import ch.systemsx.cisd.openbis.generic.shared.parser.FileSection;
 import ch.systemsx.cisd.openbis.generic.shared.parser.NamedInputStream;
 
@@ -105,59 +108,117 @@ public class MaterialUploadSectionsParser
                 new ArrayList<BatchRegistrationResult>(uploadedFiles.size());
         for (final NamedInputStream multipartFile : uploadedFiles)
         {
-            List<FileSection> materialSections = new ArrayList<FileSection>();
-            if (materialType.isDefinedInFileEntityTypeCode())
+            if (multipartFile.getOriginalFilename().toLowerCase().endsWith("xls"))
             {
-                materialSections.addAll(FileSection.extractSections(multipartFile
-                        .getUnicodeReader()));
+                List<ExcelFileSection> materialSections = new ArrayList<ExcelFileSection>();
+                if (materialType.isDefinedInFileEntityTypeCode())
+                {
+                    materialSections.addAll(ExcelFileSection.extractSections(multipartFile
+                            .getInputStream()));
+                } else
+                {
+                    materialSections.add(ExcelFileSection.createFromInputStream(
+                            multipartFile.getInputStream(), materialType.getCode()));
+                }
+                int materialCounter = 0;
+                Map<String, String> defaults = Collections.emptyMap();
+                for (ExcelFileSection fs : materialSections)
+                {
+                    if (fs.getSectionName().equals("DEFAULT"))
+                    {
+                        defaults =
+                                Collections.unmodifiableMap(ExcelFileLoader.parseDefaults(
+                                        fs.getSheet(), fs.getBegin(), fs.getEnd()));
+                    } else
+                    {
+                        MaterialType typeFromSection = new MaterialType();
+                        typeFromSection.setCode(fs.getSectionName());
+                        final BisExcelFileLoader<NewMaterial> excelFileLoader =
+                                new BisExcelFileLoader<NewMaterial>(
+                                        new IParserObjectFactoryFactory<NewMaterial>()
+                                            {
+                                                public final IParserObjectFactory<NewMaterial> createFactory(
+                                                        final IPropertyMapper propertyMapper)
+                                                        throws ParserException
+                                                {
+                                                    return new NewMaterialParserObjectFactory(
+                                                            propertyMapper);
+                                                }
+                                            }, false);
+                        String sectionInFile =
+                                materialSections.size() == 1 ? "" : " (section:"
+                                        + fs.getSectionName() + ")";
+                        final List<NewMaterial> loadedMaterials =
+                                excelFileLoader.load(fs.getSheet(), fs.getBegin(), fs.getEnd(),
+                                        multipartFile.getOriginalFilename() + sectionInFile,
+                                        defaults);
+                        if (loadedMaterials.size() > 0)
+                        {
+                            newMaterials.add(new NewMaterialsWithTypes(typeFromSection,
+                                    loadedMaterials));
+                            materialCounter += loadedMaterials.size();
+                        }
+                    }
+                }
+                results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
+                        .format("Registration of %d material(s) is complete.", materialCounter)));
             } else
             {
-                materialSections.add(FileSection.createFromInputStream(
-                        multipartFile.getInputStream(), materialType.getCode()));
-            }
-            int materialCounter = 0;
-            Map<String, String> defaults = Collections.emptyMap();
-            for (FileSection fs : materialSections)
-            {
-                if (fs.getSectionName().equals("DEFAULT"))
+
+                List<FileSection> materialSections = new ArrayList<FileSection>();
+                if (materialType.isDefinedInFileEntityTypeCode())
                 {
-                    defaults =
-                            Collections.unmodifiableMap(TabFileLoader.parseDefaults(fs
-                                    .getContentReader()));
+                    materialSections.addAll(FileSection.extractSections(multipartFile
+                            .getUnicodeReader()));
                 } else
                 {
-                    Reader reader = fs.getContentReader();
-                    MaterialType typeFromSection = new MaterialType();
-                    typeFromSection.setCode(fs.getSectionName());
-                    BisTabFileLoader<NewMaterial> tabFileLoader =
-                            new BisTabFileLoader<NewMaterial>(
-                                    new IParserObjectFactoryFactory<NewMaterial>()
-                                        {
-                                            public final IParserObjectFactory<NewMaterial> createFactory(
-                                                    final IPropertyMapper propertyMapper)
-                                                    throws ParserException
-                                            {
-                                                return new NewMaterialParserObjectFactory(
-                                                        propertyMapper);
-                                            }
-                                        }, false);
-                    String sectionInFile =
-                            materialSections.size() == 1 ? "" : " (section:" + fs.getSectionName()
-                                    + ")";
-                    final List<NewMaterial> loadedMaterials =
-                            tabFileLoader.load(
-                                    new DelegatedReader(reader, multipartFile.getOriginalFilename()
-                                            + sectionInFile), defaults);
-                    if (loadedMaterials.size() > 0)
+                    materialSections.add(FileSection.createFromInputStream(
+                            multipartFile.getInputStream(), materialType.getCode()));
+                }
+                int materialCounter = 0;
+                Map<String, String> defaults = Collections.emptyMap();
+                for (FileSection fs : materialSections)
+                {
+                    if (fs.getSectionName().equals("DEFAULT"))
                     {
-                        newMaterials
-                                .add(new NewMaterialsWithTypes(typeFromSection, loadedMaterials));
-                        materialCounter += loadedMaterials.size();
+                        defaults =
+                                Collections.unmodifiableMap(TabFileLoader.parseDefaults(fs
+                                        .getContentReader()));
+                    } else
+                    {
+                        Reader reader = fs.getContentReader();
+                        MaterialType typeFromSection = new MaterialType();
+                        typeFromSection.setCode(fs.getSectionName());
+                        BisTabFileLoader<NewMaterial> tabFileLoader =
+                                new BisTabFileLoader<NewMaterial>(
+                                        new IParserObjectFactoryFactory<NewMaterial>()
+                                            {
+                                                public final IParserObjectFactory<NewMaterial> createFactory(
+                                                        final IPropertyMapper propertyMapper)
+                                                        throws ParserException
+                                                {
+                                                    return new NewMaterialParserObjectFactory(
+                                                            propertyMapper);
+                                                }
+                                            }, false);
+                        String sectionInFile =
+                                materialSections.size() == 1 ? "" : " (section:"
+                                        + fs.getSectionName() + ")";
+                        final List<NewMaterial> loadedMaterials =
+                                tabFileLoader.load(
+                                        new DelegatedReader(reader, multipartFile
+                                                .getOriginalFilename() + sectionInFile), defaults);
+                        if (loadedMaterials.size() > 0)
+                        {
+                            newMaterials.add(new NewMaterialsWithTypes(typeFromSection,
+                                    loadedMaterials));
+                            materialCounter += loadedMaterials.size();
+                        }
                     }
                 }
+                results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
+                        .format("Registration of %d material(s) is complete.", materialCounter)));
             }
-            results.add(new BatchRegistrationResult(multipartFile.getOriginalFilename(), String
-                    .format("Registration of %d material(s) is complete.", materialCounter)));
         }
         return results;
     }
-- 
GitLab