/*
* jMemorize - Learning made easy (and fun) - A Leitner flashcards tool
* Copyright(C) 2004-2006 Riad Djemili
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jmemorize.core.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jmemorize.core.Card;
import jmemorize.core.Category;
import jmemorize.core.FormattedText;
import jmemorize.core.Lesson;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import com.csvreader.CsvReader.CatastrophicException;
import com.csvreader.CsvReader.FinalizedException;
/**
* A class for importing and exporting character-separated-values (CSV).
*
* @author djemili
*/
public class CsvBuilder
{
public static final String FRONTSIDE_COL = "Frontside";
public static final String FLISIDE_COL = "Flipside";
public static final String CATEGORY_COL = "Category";
public static final String LEVEL_COL = "Level";
/**
* Is thrown when the header is missing or malformatted.
*/
public static class BadHeaderException extends Exception
{
public BadHeaderException(String message)
{
super(message);
}
}
/**
* Exports the given lesson to a CSV-file with given delimiter and given
* character set.
*/
public static void exportLesson(OutputStream out, Lesson lesson,
char delimiter, Charset charset) throws IOException
{
try
{
CsvWriter writer = new CsvWriter(out, delimiter, charset);
writeHeader(writer);
List<Card> cards = lesson.getRootCategory().getCards();
for (Card card : cards)
{
writer.write(card.getFrontSide().getText().getFormatted());
writer.write(card.getBackSide().getText().getFormatted());
if (lesson.getRootCategory() == card.getCategory())
writer.write("");
else
writer.write(card.getCategory().getName());
writer.write(Integer.toString(card.getLevel()));
writer.endRecord();
}
writer.close();
}
catch (com.csvreader.CsvWriter.FinalizedException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Parses the given file that holds text values that are delimited by given
* delimiter. The values are used to contruct a lesson.
* @param delimiter the delimiter that is used to separate values in the
* file.
* @param charset the character set that the file contents are using.
* @param file the file that should be read.
* @param charset the character set that is used in the file. Use
* <code>null</code> to use the default charset.
* @param the lesson to which the contents of the file will be added.
*
* @throws IOException if the file couldn't be read or the values are
* malformatted.
*/
public static void importLesson(InputStream in, char delimiter,
Charset charset, Lesson lesson) throws IOException, BadHeaderException
{
CsvReader reader = new CsvReader(in, delimiter, charset);
Category rootCategory = lesson.getRootCategory();
Map<String, Category> categories = new HashMap<String, Category>();
List<Category> childCategories = rootCategory.getChildCategories();
for (Category category : childCategories)
{
categories.put(category.getName(), category);
}
try
{
reader.readHeaders();
String[] headers = reader.getHeaders();
validateHeader(headers);
while (reader.readRecord())
{
FormattedText frontSide = FormattedText.formatted(reader.get(FRONTSIDE_COL));
FormattedText flipSide = FormattedText.formatted(reader.get(FLISIDE_COL));
if (frontSide.getUnformatted().length() == 0 || flipSide.getUnformatted().length() == 0)
throw new IOException("You have to specify at least a front " +
"side and flip side for every card "+getLineString(reader)+".");
Card card = new Card(frontSide, flipSide);
Category category;
String categoryName = reader.get(CATEGORY_COL);
if (categoryName.length() == 0 ||
categoryName.equalsIgnoreCase(rootCategory.getName()))
{
category = rootCategory;
}
else
{
if (categories.containsKey(categoryName))
{
category = (Category)categories.get(categoryName);
}
else
{
category = new Category(categoryName);
rootCategory.addCategoryChild(category);
categories.put(categoryName, category);
}
}
String level = reader.get(LEVEL_COL);
if (level.length() > 0)
{
category.addCard(card, Integer.parseInt(level));
}
else
{
category.addCard(card);
}
}
reader.close();
}
catch (FinalizedException e)
{
throw new IOException(e.toString());
}
catch (CatastrophicException e)
{
throw new IOException(e.toString());
}
}
private static void writeHeader(CsvWriter writer) throws IOException,
com.csvreader.CsvWriter.FinalizedException
{
writer.write(FRONTSIDE_COL);
writer.write(FLISIDE_COL);
writer.write(CATEGORY_COL);
writer.write(LEVEL_COL);
writer.endRecord();
}
private static void validateHeader(String[] headers) throws BadHeaderException
{
boolean hasFront = false;
boolean hasFlip = false;
for (int i = 0; i < headers.length; i++)
{
if (headers[i].equalsIgnoreCase(FRONTSIDE_COL))
{
hasFront = true;
continue;
}
if (headers[i].equalsIgnoreCase(FLISIDE_COL))
{
hasFlip = true;
continue;
}
if (!headers[i].equalsIgnoreCase(CATEGORY_COL) &&
!headers[i].equalsIgnoreCase(LEVEL_COL))
throw new BadHeaderException("Unknown header column: "+headers[i]);
}
if (!hasFront || !hasFlip)
throw new BadHeaderException("The first line needs to specify "+
"the header, which must have at least contain the columns '"+
FRONTSIDE_COL+ "' and '"+FLISIDE_COL+"'.");
}
private static String getLineString(CsvReader reader)
{
return "(line "+ reader.getCurrentRecord() +")";
}
}