* see method description
*/
public static void main(String[] args) {
+ new Main().start(args);
+ }
+
+ /**
+ * Start the default handling for the application.
+ * <p>
+ * If specific actions were asked (with correct parameters), they will be
+ * forwarded to the different protected methods that you can override.
+ * <p>
+ * At the end of the method, {@link Main#exit(int)} will be called; by
+ * default, it calls {@link System#exit(int)} if the status is not 0.
+ *
+ * @param args
+ * the arguments received from the system
+ */
+ public void start(String [] args) {
// Only one line, but very important:
Instance.init();
Progress pg = new Progress();
mainProgress.addProgress(pg, mainProgress.getMax());
- VersionCheck updates = VersionCheck.check();
- if (updates.isNewVersionAvailable()) {
- // Sent to syserr so not to cause problem if one tries to capture a
- // story content in text mode
- System.err
- .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
- System.err.println("");
- for (Version v : updates.getNewer()) {
- System.err.println("\tVersion " + v);
- System.err.println("\t-------------");
- System.err.println("");
- for (String it : updates.getChanges().get(v)) {
- System.err.println("\t- " + it);
- }
- System.err.println("");
- }
- }
+ VersionCheck updates = checkUpdates();
if (exitCode == 0) {
switch (action) {
case IMPORT:
- exitCode = imprt(urlString, pg);
- updates.ok(); // we consider it read
+ if (updates != null)
+ updates.ok(); // we consider it read
+
+ try {
+ exitCode = imprt(BasicReader.getUrl(urlString), pg);
+ } catch (MalformedURLException e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ exitCode = 1;
+ }
+
break;
case EXPORT:
- exitCode = export(luid, sourceString, target, pg);
- updates.ok(); // we consider it read
+ if (updates != null)
+ updates.ok(); // we consider it read
+
+ OutputType exportType = OutputType.valueOfNullOkUC(sourceString, null);
+ if (exportType == null) {
+ Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, sourceString)));
+ exitCode = 1;
+ break;
+ }
+
+ exitCode = export(luid, exportType, target, pg);
+
break;
case CONVERT:
- exitCode = convert(urlString, sourceString, target,
+ if (updates != null)
+ updates.ok(); // we consider it read
+
+ OutputType convertType = OutputType.valueOfAllOkUC(sourceString, null);
+ if (convertType == null) {
+ Instance.getInstance().getTraceHandler()
+ .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, sourceString)));
+
+ exitCode = 2;
+ break;
+ }
+
+ exitCode = convert(urlString, convertType, target,
plusInfo == null ? false : plusInfo, pg);
- updates.ok(); // we consider it read
+
break;
case LIST:
exitCode = list(sourceString);
}
try {
+ Integer chap = null;
+ if (chapString != null) {
+ try {
+ chap = Integer.parseInt(chapString);
+ } catch (NumberFormatException e) {
+ Instance.getInstance().getTraceHandler().error(new IOException(
+ "Chapter number cannot be parsed: " + chapString, e));
+ exitCode = 2;
+ break;
+ }
+ }
+
BasicLibrary lib = Instance.getInstance().getLibrary();
- exitCode = read(lib.getStory(luid, null), chapString);
+ exitCode = read(lib.getStory(luid, null), chap);
} catch (IOException e) {
Instance.getInstance().getTraceHandler()
.error(new IOException("Failed to read book", e));
}
try {
+ Integer chap = null;
+ if (chapString != null) {
+ try {
+ chap = Integer.parseInt(chapString);
+ } catch (NumberFormatException e) {
+ Instance.getInstance().getTraceHandler().error(new IOException(
+ "Chapter number cannot be parsed: " + chapString, e));
+ exitCode = 2;
+ break;
+ }
+ }
+
BasicSupport support = BasicSupport
.getSupport(BasicReader.getUrl(urlString));
if (support == null) {
break;
}
- exitCode = read(support.process(null), chapString);
+ exitCode = read(support.process(null), chap);
} catch (IOException e) {
Instance.getInstance().getTraceHandler()
.error(new IOException("Failed to read book", e));
break;
}
- try {
- if (searchOn == null) {
- new CliReader().listSearchables();
- } else if (search != null) {
-
- new CliReader().searchBooksByKeyword(searchOn, search, page,
- item);
- } else {
- exitCode = 255;
+ if (searchOn == null) {
+ try {
+ search();
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ exitCode = 1;
}
- } catch (IOException e1) {
- Instance.getInstance().getTraceHandler().error(e1);
- exitCode = 20;
+ } else if (search != null) {
+ try {
+ searchKeywords(searchOn, search, page, item);
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ exitCode = 20;
+ }
+ } else {
+ exitCode = 255;
}
break;
}
try {
- new CliReader().searchBooksByTag(searchOn, page, item,
- tags.toArray(new Integer[] {}));
- } catch (IOException e1) {
- Instance.getInstance().getTraceHandler().error(e1);
+ searchTags(searchOn, page, item,
+ tags.toArray(new Integer[] {}));
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(e);
}
break;
exitCode = 0;
break;
case VERSION:
+ if (updates != null)
+ updates.ok(); // we consider it read
+
System.out
.println(String.format("Fanfix version %s"
+ "%nhttps://github.com/nikiroo/fanfix/"
+ "%n\tWritten by Nikiroo",
Version.getCurrentVersion()));
- updates.ok(); // we consider it read
break;
case START:
try {
- new CliReader().listBooks(null);
+ start();
} catch (IOException e) {
Instance.getInstance().getTraceHandler().error(e);
exitCode = 66;
break;
}
try {
- ServerObject server = new RemoteLibraryServer(key, port);
- server.setTraceHandler(Instance.getInstance().getTraceHandler());
- server.run();
+ startServer(key, port);
} catch (IOException e) {
Instance.getInstance().getTraceHandler().error(e);
}
- return;
+
+ break;
case STOP_SERVER:
// Can be given via "--remote XX XX XX"
if (key == null)
break;
}
try {
- new RemoteLibrary(key, host, port).exit();
+ stopServer(key, host, port);
} catch (SSLException e) {
Instance.getInstance().getTraceHandler().error(
"Bad access key for remote library");
try {
Instance.getInstance().getTempFiles().close();
} catch (IOException e) {
- Instance.getInstance().getTraceHandler().error(new IOException("Cannot dispose of the temporary files", e));
+ Instance.getInstance().getTraceHandler().error(new IOException(
+ "Cannot dispose of the temporary files", e));
}
if (exitCode == 255) {
syntax(false);
}
- System.exit(exitCode);
+ exit(exitCode);
+ }
+
+ /**
+ * A normal invocation of the program (without parameters or at least
+ * without "action" parameters).
+ * <p>
+ * You will probably want to override that one if you offer a user
+ * interface.
+ *
+ * @throws IOException
+ * in case of I/O error
+ */
+ protected void start() throws IOException {
+ new CliReader().listBooks(null);
+ }
+
+ /**
+ * Will check if updates are available.
+ * <p>
+ * For this, it will simply forward the call to
+ * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix".
+ *
+ * @return the newer version information or NULL if nothing new
+ */
+ protected VersionCheck checkUpdates() {
+ return checkUpdates("nikiroo/fanfix");
+ }
+
+ /**
+ * Will check if updates are available on a specific GitHub project.
+ * <p>
+ * Will be called by {@link Main#checkUpdates()}, but if you override that
+ * one you mall call it with another project.
+ *
+ * @param githubProject
+ * the GitHub project, for instance "nikiroo/fanfix"
+ *
+ * @return the newer version information or NULL if nothing new
+ */
+ protected VersionCheck checkUpdates(String githubProject) {
+ VersionCheck updates = VersionCheck.check(githubProject);
+ if (updates.isNewVersionAvailable()) {
+ notifyUpdates(updates);
+ return updates;
+ }
+
+ return null;
}
+ /**
+ * Notify the user about available updates.
+ * <p>
+ * Will only be called when a version is available.
+ * <p>
+ * Note that you can call {@link VersionCheck#ok()} on it if the user has
+ * read the information (by default, it is marked read only on certain other
+ * actions).
+ *
+ * @param updates
+ * the new version information
+ */
+ protected void notifyUpdates(VersionCheck updates) {
+ // Sent to syserr so not to cause problem if one tries to capture a
+ // story content in text mode
+ System.err.println(
+ "A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
+ System.err.println("");
+ for (Version v : updates.getNewer()) {
+ System.err.println("\tVersion " + v);
+ System.err.println("\t-------------");
+ System.err.println("");
+ for (String it : updates.getChanges().get(v)) {
+ System.err.println("\t- " + it);
+ }
+ System.err.println("");
+ }
+ }
+
/**
* Import the given resource into the {@link LocalLibrary}.
*
- * @param urlString
+ * @param url
* the resource to import
* @param pg
* the optional progress reporter
*
* @return the exit return code (0 = success)
*/
- public static int imprt(String urlString, Progress pg) {
+ protected static int imprt(URL url, Progress pg) {
try {
- MetaData meta = Instance.getInstance().getLibrary().imprt(BasicReader.getUrl(urlString), pg);
+ MetaData meta = Instance.getInstance().getLibrary().imprt(url, pg);
System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported.");
} catch (IOException e) {
Instance.getInstance().getTraceHandler().error(e);
*
* @param luid
* the story LUID
- * @param typeString
+ * @param type
* the {@link OutputType} to use
* @param target
* the target
*
* @return the exit return code (0 = success)
*/
- public static int export(String luid, String typeString, String target,
+ protected static int export(String luid, OutputType type, String target,
Progress pg) {
- OutputType type = OutputType.valueOfNullOkUC(typeString, null);
- if (type == null) {
- Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, typeString)));
- return 1;
- }
-
try {
Instance.getInstance().getLibrary().export(luid, type, target, pg);
} catch (IOException e) {
return 0;
}
-
+
/**
* List the stories of the given source from the {@link LocalLibrary}
* (unless NULL is passed, in which case all stories will be listed).
*
* @return the exit return code (0 = success)
*/
- private static int list(String source) {
+ protected int list(String source) {
try {
new CliReader().listBooks(source);
} catch (IOException e) {
*
* @param story
* the story to read
- * @param chapString
+ * @param chap
* which {@link Chapter} to read (starting at 1), or NULL to get
* the {@link Story} description
*
* @return the exit return code (0 = success)
*/
- private static int read(Story story, String chapString) {
- Integer chap = null;
- if (chapString != null) {
- try {
- chap = Integer.parseInt(chapString);
- } catch (NumberFormatException e) {
- Instance.getInstance().getTraceHandler().error(new IOException(
- "Chapter number cannot be parsed: " + chapString, e));
- return 2;
- }
- }
-
+ protected int read(Story story, Integer chap) {
if (story != null) {
try {
if (chap == null) {
}
} else {
Instance.getInstance().getTraceHandler()
- .error("Cannot find book: " + chapString);
+ .error("Cannot find book: " + story);
return 2;
}
*
* @param urlString
* the source {@link Story} to convert
- * @param typeString
+ * @param type
* the {@link OutputType} to convert to
* @param target
* the target file
*
* @return the exit return code (0 = success)
*/
- public static int convert(String urlString, String typeString,
+ protected int convert(String urlString, OutputType type,
String target, boolean infoCover, Progress pg) {
int exitCode = 0;
sourceName = sourceName.substring("file://".length());
}
- OutputType type = OutputType.valueOfAllOkUC(typeString, null);
- if (type == null) {
- Instance.getInstance().getTraceHandler()
- .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, typeString)));
+ try {
+ BasicSupport support = BasicSupport.getSupport(source);
- exitCode = 2;
- } else {
- try {
- BasicSupport support = BasicSupport.getSupport(source);
-
- if (support != null) {
- Instance.getInstance().getTraceHandler().trace("Support found: " + support.getClass());
- Progress pgIn = new Progress();
- Progress pgOut = new Progress();
- if (pg != null) {
- pg.setMax(2);
- pg.addProgress(pgIn, 1);
- pg.addProgress(pgOut, 1);
- }
+ if (support != null) {
+ Instance.getInstance().getTraceHandler()
+ .trace("Support found: " + support.getClass());
+ Progress pgIn = new Progress();
+ Progress pgOut = new Progress();
+ if (pg != null) {
+ pg.setMax(2);
+ pg.addProgress(pgIn, 1);
+ pg.addProgress(pgOut, 1);
+ }
- Story story = support.process(pgIn);
- try {
- target = new File(target).getAbsolutePath();
- BasicOutput.getOutput(type, infoCover, infoCover).process(story, target, pgOut);
- } catch (IOException e) {
- Instance.getInstance().getTraceHandler()
- .error(new IOException(trans(StringId.ERR_SAVING, target), e));
- exitCode = 5;
- }
- } else {
+ Story story = support.process(pgIn);
+ try {
+ target = new File(target).getAbsolutePath();
+ BasicOutput.getOutput(type, infoCover, infoCover)
+ .process(story, target, pgOut);
+ } catch (IOException e) {
Instance.getInstance().getTraceHandler()
- .error(new IOException(trans( StringId.ERR_NOT_SUPPORTED, source)));
-
- exitCode = 4;
+ .error(new IOException(
+ trans(StringId.ERR_SAVING, target), e));
+ exitCode = 5;
}
- } catch (IOException e) {
+ } else {
Instance.getInstance().getTraceHandler()
- .error(new IOException(trans(StringId.ERR_LOADING, sourceName), e));
- exitCode = 3;
+ .error(new IOException(
+ trans(StringId.ERR_NOT_SUPPORTED, source)));
+
+ exitCode = 4;
}
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(new IOException(
+ trans(StringId.ERR_LOADING, sourceName), e));
+ exitCode = 3;
}
} catch (MalformedURLException e) {
Instance.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e));
return exitCode;
}
- /**
- * Simple shortcut method to call {link Instance#getTrans()#getString()}.
- *
- * @param id
- * the ID to translate
- *
- * @return the translated result
- */
- private static String trans(StringId id, Object... params) {
- return Instance.getInstance().getTrans().getString(id, params);
- }
-
/**
* Display the correct syntax of the program to the user to stdout, or an
* error message if the syntax used was wrong on stderr.
* @param showHelp
* TRUE to show the syntax help, FALSE to show "syntax error"
*/
- private static void syntax(boolean showHelp) {
+ protected void syntax(boolean showHelp) {
if (showHelp) {
StringBuilder builder = new StringBuilder();
for (SupportType type : SupportType.values()) {
System.err.println(trans(StringId.ERR_SYNTAX));
}
}
+
+ /**
+ * Starts a search operation (i.e., list the available web sites we can
+ * search on).
+ *
+ * @throws IOException
+ * in case of I/O errors
+ */
+ protected void search() throws IOException {
+ new CliReader().listSearchables();
+ }
+
+ /**
+ * Search for books by keywords on the given supported web site.
+ *
+ * @param searchOn
+ * the web site to search on
+ * @param search
+ * the keyword to look for
+ * @param page
+ * the page of results to get, or 0 to inquire about the number
+ * of pages
+ * @param item
+ * the index of the book we are interested by, or 0 to query
+ * about how many books are in that page of results
+ *
+ * @throws IOException
+ * in case of I/O error
+ */
+ protected void searchKeywords(SupportType searchOn, String search,
+ int page, Integer item) throws IOException {
+ new CliReader().searchBooksByKeyword(searchOn, search, page, item);
+ }
+
+ /**
+ * Search for books by tags on the given supported web site.
+ *
+ * @param searchOn
+ * the web site to search on
+ * @param page
+ * the page of results to get, or 0 to inquire about the number
+ * of pages
+ * @param item
+ * the index of the book we are interested by, or 0 to query
+ * about how many books are in that page of results
+ * @param tags
+ * the tags to look for
+ *
+ * @throws IOException
+ * in case of I/O error
+ */
+ protected void searchTags(SupportType searchOn, Integer page, Integer item,
+ Integer[] tags) throws IOException {
+ new CliReader().searchBooksByTag(searchOn, page, item, tags);
+ }
+
+ /**
+ * Start a Fanfix server.
+ *
+ * @param key
+ * the key taht will be needed to contact the Fanfix server
+ * @param port
+ * the port on which to run
+ *
+ * @throws IOException
+ * in case of I/O errors
+ * @throws SSLException
+ * when the key was not accepted
+ */
+ private void startServer(String key, int port) throws IOException {
+ ServerObject server = new RemoteLibraryServer(key, port);
+ server.setTraceHandler(Instance.getInstance().getTraceHandler());
+ server.run();
+ }
+
+ /**
+ * Stop a running Fanfix server.
+ *
+ * @param key
+ * the key to contact the Fanfix server
+ * @param host
+ * the host on which it runs (NULL means localhost)
+ * @param port
+ * the port on which it runs
+ *
+ * @throws IOException
+ * in case of I/O errors
+ * @throws SSLException
+ * when the key was not accepted
+ */
+ private void stopServer(
+ String key, String host, Integer port)
+ throws IOException, SSLException {
+ new RemoteLibrary(key, host, port).exit();
+ }
+
+ /**
+ * We are done and ready to exit.
+ * <p>
+ * By default, it will call {@link System#exit(int)} if the status is not 0.
+ *
+ * @param status
+ * the exit status
+ */
+ protected void exit(int status) {
+ if (status != 0) {
+ System.exit(status);
+ }
+ }
+
+ /**
+ * Simple shortcut method to call {link Instance#getTrans()#getString()}.
+ *
+ * @param id
+ * the ID to translate
+ *
+ * @return the translated result
+ */
+ static private String trans(StringId id, Object... params) {
+ return Instance.getInstance().getTrans().getString(id, params);
+ }
}
* @author niki
*/
public class VersionCheck {
- private static final String base = "https://github.com/nikiroo/fanfix/raw/master/changelog${LANG}.md";
+ private static final String base = "https://github.com/${PROJECT}/raw/master/changelog${LANG}.md";
private Version current;
private List<Version> newer;
* Check if there are available {@link Version}s of this program more recent
* than the current one.
*
+ * @param githubProject
+ * the GitHub project to check on, for instance "nikiroo/fanfix"
+ *
* @return a {@link VersionCheck}
*/
- public static VersionCheck check() {
+ public static VersionCheck check(String githubProject) {
Version current = Version.getCurrentVersion();
List<Version> newer = new ArrayList<Version>();
Map<Version, List<String>> changes = new HashMap<Version, List<String>>();
if (Instance.getInstance().isVersionCheckNeeded()) {
try {
- // Prepare the URLs according to the user's language
+ // Use the right project:
+ String base = VersionCheck.base.replace("${PROJECT}",
+ githubProject);
+
+ // Prepare the URLs according to the user's language:
Locale lang = Instance.getInstance().getTrans().getLocale();
String fr = lang.getLanguage();
String BE = lang.getCountry().replace(".UTF8", "");
* Stop the server.
*
* @throws IOException
- * in case of I/O error (including bad key)
+ * in case of I/O errors
+ * @throws SSLException
+ * when the key was not accepted
*/
- public void exit() throws IOException {
+ public void exit() throws IOException, SSLException {
connectRemoteAction(new RemoteAction() {
@Override
public void action(ConnectActionClientObject action)
--- /dev/null
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringId;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix.supported.BasicSupport_Deprecated;
+import be.nikiroo.fanfix.supported.SupportType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BasicSupportDeprecatedTest extends TestLauncher {
+ // quote chars
+ private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE);
+ private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE);
+ private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+ private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+ public BasicSupportDeprecatedTest(String[] args) {
+ super("BasicSupportDeprecated", args);
+
+ addSeries(new TestLauncher("General", args) {
+ {
+ addTest(new TestCase("BasicSupport.makeParagraphs()") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+
+ @Override
+ public void fixBlanksBreaks(List<Paragraph> paras) {
+ }
+
+ @Override
+ public List<Paragraph> requotify(Paragraph para) {
+ List<Paragraph> paras = new ArrayList<Paragraph>(
+ 1);
+ paras.add(para);
+ return paras;
+ }
+ };
+
+ List<Paragraph> paras = null;
+
+ paras = support.makeParagraphs(null, "", null);
+ assertEquals(
+ "An empty content should not generate paragraphs",
+ 0, paras.size());
+
+ paras = support.makeParagraphs(null,
+ "Line 1</p><p>Line 2</p><p>Line 3</p>", null);
+ assertEquals(5, paras.size());
+ assertEquals("Line 1", paras.get(0).getContent());
+ assertEquals(ParagraphType.BLANK, paras.get(1)
+ .getType());
+ assertEquals("Line 2", paras.get(2).getContent());
+ assertEquals(ParagraphType.BLANK, paras.get(3)
+ .getType());
+ assertEquals("Line 3", paras.get(4).getContent());
+
+ paras = support.makeParagraphs(null,
+ "<p>Line1</p><p>Line2</p><p>Line3</p>", null);
+ assertEquals(6, paras.size());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.removeDoubleBlanks()") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+ };
+
+ List<Paragraph> paras = null;
+
+ paras = support
+ .makeParagraphs(
+ null,
+ "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>",
+ null);
+ assertEquals(5, paras.size());
+
+ paras = support
+ .makeParagraphs(
+ null,
+ "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>* * *",
+ null);
+ assertEquals(5, paras.size());
+
+ paras = support.makeParagraphs(null, "1<p>* * *<p>2",
+ null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null,
+ "1<p><br/><p>* * *<p>2", null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null,
+ "1<p>* * *<br/><p><br><p>2", null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null,
+ "1<p><br/><br>* * *<br/><p><br><p>2", null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() quotes") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+ };
+
+ Paragraph para;
+
+ // sanity check
+ para = support.processPara("");
+ assertEquals(ParagraphType.BLANK, para.getType());
+ //
+
+ para = support.processPara("\"Yes, my Lord!\"");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("«Yes, my Lord!»");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("'Yes, my Lord!'");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+ para.getContent());
+
+ para = support.processPara("‹Yes, my Lord!›");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+ para.getContent());
+ }
+ });
+
+ addTest(new TestCase(
+ "BasicSupport.processPara() double-simple quotes") {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+
+ super.tearDown();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+ };
+
+ Paragraph para;
+
+ para = support.processPara("''Yes, my Lord!''");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("‹‹Yes, my Lord!››");
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() apostrophe") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+ };
+
+ Paragraph para;
+
+ String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge";
+ para = support.processPara(text);
+ assertEquals(ParagraphType.NORMAL, para.getType());
+ assertEquals(text, para.getContent());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() words count") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty() {
+ @Override
+ protected boolean isHtml() {
+ return true;
+ }
+ };
+
+ Paragraph para;
+
+ para = support.processPara("«Yes, my Lord!»");
+ assertEquals(3, para.getWords());
+
+ para = support.processPara("One, twee, trois.");
+ assertEquals(3, para.getWords());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.requotify() words count") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportEmpty support = new BasicSupportEmpty();
+
+ char openDoubleQuote = Instance.getInstance().getTrans()
+ .getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+ char closeDoubleQuote = Instance.getInstance().getTrans()
+ .getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+ String content = null;
+ Paragraph para = null;
+ List<Paragraph> paras = null;
+ long words = 0;
+
+ content = "One, twee, trois.";
+ para = new Paragraph(ParagraphType.NORMAL, content,
+ content.split(" ").length);
+ paras = support.requotify(para);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals("Bad words count in a single paragraph",
+ para.getWords(), words);
+
+ content = "Such WoW! So Web2.0! With Colours!";
+ para = new Paragraph(ParagraphType.NORMAL, content,
+ content.split(" ").length);
+ paras = support.requotify(para);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals("Bad words count in a single paragraph",
+ para.getWords(), words);
+
+ content = openDoubleQuote + "Such a good idea!"
+ + closeDoubleQuote
+ + ", she said. This ought to be a new para.";
+ para = new Paragraph(ParagraphType.QUOTE, content,
+ content.split(" ").length);
+ paras = support.requotify(para);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals(
+ "Bad words count in a requotified paragraph",
+ para.getWords(), words);
+ }
+ });
+ }
+ });
+
+ addSeries(new TestLauncher("Text", args) {
+ {
+ addTest(new TestCase("Chapter detection simple") {
+ private File tmp;
+
+ @Override
+ public void setUp() throws Exception {
+ tmp = File.createTempFile("fanfix-text-file_", ".test");
+ IOUtils.writeSmallFile(tmp.getParentFile(),
+ tmp.getName(), "TITLE"
+ + "\n"//
+ + "By nona"
+ + "\n" //
+ + "\n" //
+ + "Chapter 0: Resumé" + "\n" + "\n"
+ + "'sume." + "\n" + "\n"
+ + "Chapter 1: chap1" + "\n" + "\n"
+ + "Fanfan." + "\n" + "\n"
+ + "Chapter 2: Chap2" + "\n" + "\n" //
+ + "Tulipe." + "\n");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ tmp.delete();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupport support = BasicSupport.getSupport(
+ SupportType.TEXT, tmp.toURI().toURL());
+
+ Story story = support.process(null);
+
+ assertEquals(2, story.getChapters().size());
+ assertEquals(1, story.getChapters().get(1)
+ .getParagraphs().size());
+ assertEquals("Tulipe.", story.getChapters().get(1)
+ .getParagraphs().get(0).getContent());
+ }
+ });
+
+ addTest(new TestCase("Chapter detection with String 'Chapter'") {
+ private File tmp;
+
+ @Override
+ public void setUp() throws Exception {
+ tmp = File.createTempFile("fanfix-text-file_", ".test");
+ IOUtils.writeSmallFile(tmp.getParentFile(),
+ tmp.getName(), "TITLE"
+ + "\n"//
+ + "By nona"
+ + "\n" //
+ + "\n" //
+ + "Chapter 0: Resumé" + "\n" + "\n"
+ + "'sume." + "\n" + "\n"
+ + "Chapter 1: chap1" + "\n" + "\n"
+ + "Chapter fout-la-merde" + "\n"
+ + "\n"//
+ + "Fanfan." + "\n" + "\n"
+ + "Chapter 2: Chap2" + "\n" + "\n" //
+ + "Tulipe." + "\n");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ tmp.delete();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupport support = BasicSupport.getSupport(
+ SupportType.TEXT, tmp.toURI().toURL());
+
+ Story story = support.process(null);
+
+ assertEquals(2, story.getChapters().size());
+ assertEquals(1, story.getChapters().get(1)
+ .getParagraphs().size());
+ assertEquals("Tulipe.", story.getChapters().get(1)
+ .getParagraphs().get(0).getContent());
+ }
+ });
+ }
+ });
+ }
+
+ private class BasicSupportEmpty extends BasicSupport_Deprecated {
+ @Override
+ protected boolean supports(URL url) {
+ return false;
+ }
+
+ @Override
+ protected boolean isHtml() {
+ return false;
+ }
+
+ @Override
+ protected MetaData getMeta(URL source, InputStream in)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ protected String getDesc(URL source, InputStream in) throws IOException {
+ return null;
+ }
+
+ @Override
+ protected List<Entry<String, URL>> getChapters(URL source,
+ InputStream in, Progress pg) throws IOException {
+ return null;
+ }
+
+ @Override
+ protected String getChapterContent(URL source, InputStream in,
+ int number, Progress pg) throws IOException {
+ return null;
+ }
+
+ @Override
+ // and make it public!
+ public List<Paragraph> makeParagraphs(URL source, String content,
+ Progress pg) throws IOException {
+ return super.makeParagraphs(source, content, pg);
+ }
+
+ @Override
+ // and make it public!
+ public void fixBlanksBreaks(List<Paragraph> paras) {
+ super.fixBlanksBreaks(paras);
+ }
+
+ @Override
+ // and make it public!
+ public Paragraph processPara(String line) {
+ return super.processPara(line);
+ }
+
+ @Override
+ // and make it public!
+ public List<Paragraph> requotify(Paragraph para) {
+ return super.requotify(para);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringId;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix.supported.BasicSupportHelper;
+import be.nikiroo.fanfix.supported.BasicSupportImages;
+import be.nikiroo.fanfix.supported.BasicSupportPara;
+import be.nikiroo.fanfix.supported.SupportType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BasicSupportUtilitiesTest extends TestLauncher {
+ // quote chars
+ private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE);
+ private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE);
+ private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+ private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+ public BasicSupportUtilitiesTest(String[] args) {
+ super("BasicSupportUtilities", args);
+
+ addSeries(new TestLauncher("General", args) {
+ {
+ addTest(new TestCase("BasicSupport.makeParagraphs()") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic bsPara = new BasicSupportParaPublic() {
+ @Override
+ public void fixBlanksBreaks(List<Paragraph> paras) {
+ }
+
+ @Override
+ public List<Paragraph> requotify(Paragraph para, boolean html) {
+ List<Paragraph> paras = new ArrayList<Paragraph>(
+ 1);
+ paras.add(para);
+ return paras;
+ }
+ };
+
+ List<Paragraph> paras = null;
+
+ paras = bsPara.makeParagraphs(null, null, "", true, null);
+ assertEquals(
+ "An empty content should not generate paragraphs",
+ 0, paras.size());
+
+ paras = bsPara.makeParagraphs(null, null,
+ "Line 1</p><p>Line 2</p><p>Line 3</p>", true, null);
+ assertEquals(5, paras.size());
+ assertEquals("Line 1", paras.get(0).getContent());
+ assertEquals(ParagraphType.BLANK, paras.get(1)
+ .getType());
+ assertEquals("Line 2", paras.get(2).getContent());
+ assertEquals(ParagraphType.BLANK, paras.get(3)
+ .getType());
+ assertEquals("Line 3", paras.get(4).getContent());
+
+ paras = bsPara.makeParagraphs(null, null,
+ "<p>Line1</p><p>Line2</p><p>Line3</p>", true, null);
+ assertEquals(6, paras.size());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.removeDoubleBlanks()") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ List<Paragraph> paras = null;
+
+ paras = support
+ .makeParagraphs(
+ null,
+ null,
+ "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>",
+ true,
+ null);
+ assertEquals(5, paras.size());
+
+ paras = support
+ .makeParagraphs(
+ null,
+ null,
+ "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>* * *",
+ true,
+ null);
+ assertEquals(5, paras.size());
+
+ paras = support.makeParagraphs(null, null, "1<p>* * *<p>2",
+ true, null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null, null,
+ "1<p><br/><p>* * *<p>2", true, null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null, null,
+ "1<p>* * *<br/><p><br><p>2", true, null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+
+ paras = support.makeParagraphs(null, null,
+ "1<p><br/><br>* * *<br/><p><br><p>2", true, null);
+ assertEquals(3, paras.size());
+ assertEquals(ParagraphType.BREAK, paras.get(1)
+ .getType());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() quotes") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ Paragraph para;
+
+ // sanity check
+ para = support.processPara("", true);
+ assertEquals(ParagraphType.BLANK, para.getType());
+ //
+
+ para = support.processPara("\"Yes, my Lord!\"", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("«Yes, my Lord!»", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("'Yes, my Lord!'", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+ para.getContent());
+
+ para = support.processPara("‹Yes, my Lord!›", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+ para.getContent());
+ }
+ });
+
+ addTest(new TestCase(
+ "BasicSupport.processPara() double-simple quotes") {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+
+ super.tearDown();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ Paragraph para;
+
+ para = support.processPara("''Yes, my Lord!''", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+
+ para = support.processPara("‹‹Yes, my Lord!››", true);
+ assertEquals(ParagraphType.QUOTE, para.getType());
+ assertEquals(openDoubleQuote + "Yes, my Lord!"
+ + closeDoubleQuote, para.getContent());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() apostrophe") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ Paragraph para;
+
+ String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge";
+ para = support.processPara(text, true);
+ assertEquals(ParagraphType.NORMAL, para.getType());
+ assertEquals(text, para.getContent());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.processPara() words count") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ Paragraph para;
+
+ para = support.processPara("«Yes, my Lord!»", true);
+ assertEquals(3, para.getWords());
+
+ para = support.processPara("One, twee, trois.", true);
+ assertEquals(3, para.getWords());
+ }
+ });
+
+ addTest(new TestCase("BasicSupport.requotify() words count") {
+ @Override
+ public void test() throws Exception {
+ BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+ char openDoubleQuote = Instance.getInstance().getTrans()
+ .getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+ char closeDoubleQuote = Instance.getInstance().getTrans()
+ .getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+ String content = null;
+ Paragraph para = null;
+ List<Paragraph> paras = null;
+ long words = 0;
+
+ content = "One, twee, trois.";
+ para = new Paragraph(ParagraphType.NORMAL, content,
+ content.split(" ").length);
+ paras = support.requotify(para, false);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals("Bad words count in a single paragraph",
+ para.getWords(), words);
+
+ content = "Such WoW! So Web2.0! With Colours!";
+ para = new Paragraph(ParagraphType.NORMAL, content,
+ content.split(" ").length);
+ paras = support.requotify(para, false);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals("Bad words count in a single paragraph",
+ para.getWords(), words);
+
+ content = openDoubleQuote + "Such a good idea!"
+ + closeDoubleQuote
+ + ", she said. This ought to be a new para.";
+ para = new Paragraph(ParagraphType.QUOTE, content,
+ content.split(" ").length);
+ paras = support.requotify(para, false);
+ words = 0;
+ for (Paragraph p : paras) {
+ words += p.getWords();
+ }
+ assertEquals(
+ "Bad words count in a requotified paragraph",
+ para.getWords(), words);
+ }
+ });
+ }
+ });
+
+ addSeries(new TestLauncher("Text", args) {
+ {
+ addTest(new TestCase("Chapter detection simple") {
+ private File tmp;
+
+ @Override
+ public void setUp() throws Exception {
+ tmp = File.createTempFile("fanfix-text-file_", ".test");
+ IOUtils.writeSmallFile(tmp.getParentFile(),
+ tmp.getName(), "TITLE"
+ + "\n"//
+ + "By nona"
+ + "\n" //
+ + "\n" //
+ + "Chapter 0: Resumé" + "\n" + "\n"
+ + "'sume." + "\n" + "\n"
+ + "Chapter 1: chap1" + "\n" + "\n"
+ + "Fanfan." + "\n" + "\n"
+ + "Chapter 2: Chap2" + "\n" + "\n" //
+ + "Tulipe." + "\n");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ tmp.delete();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupport support = BasicSupport.getSupport(
+ SupportType.TEXT, tmp.toURI().toURL());
+
+ Story story = support.process(null);
+
+ assertEquals(2, story.getChapters().size());
+ assertEquals(1, story.getChapters().get(1)
+ .getParagraphs().size());
+ assertEquals("Tulipe.", story.getChapters().get(1)
+ .getParagraphs().get(0).getContent());
+ }
+ });
+
+ addTest(new TestCase("Chapter detection with String 'Chapter'") {
+ private File tmp;
+
+ @Override
+ public void setUp() throws Exception {
+ tmp = File.createTempFile("fanfix-text-file_", ".test");
+ IOUtils.writeSmallFile(tmp.getParentFile(),
+ tmp.getName(), "TITLE"
+ + "\n"//
+ + "By nona"
+ + "\n" //
+ + "\n" //
+ + "Chapter 0: Resumé" + "\n" + "\n"
+ + "'sume." + "\n" + "\n"
+ + "Chapter 1: chap1" + "\n" + "\n"
+ + "Chapter fout-la-merde" + "\n"
+ + "\n"//
+ + "Fanfan." + "\n" + "\n"
+ + "Chapter 2: Chap2" + "\n" + "\n" //
+ + "Tulipe." + "\n");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ tmp.delete();
+ }
+
+ @Override
+ public void test() throws Exception {
+ BasicSupport support = BasicSupport.getSupport(
+ SupportType.TEXT, tmp.toURI().toURL());
+
+ Story story = support.process(null);
+
+ assertEquals(2, story.getChapters().size());
+ assertEquals(1, story.getChapters().get(1)
+ .getParagraphs().size());
+ assertEquals("Tulipe.", story.getChapters().get(1)
+ .getParagraphs().get(0).getContent());
+ }
+ });
+ }
+ });
+ }
+
+ class BasicSupportParaPublic extends BasicSupportPara {
+ public BasicSupportParaPublic() {
+ super(new BasicSupportHelper(), new BasicSupportImages());
+ }
+
+ @Override
+ // and make it public!
+ public Paragraph makeParagraph(BasicSupport support, URL source,
+ String line, boolean html) {
+ return super.makeParagraph(support, source, line, html);
+ }
+
+ @Override
+ // and make it public!
+ public List<Paragraph> makeParagraphs(BasicSupport support,
+ URL source, String content, boolean html, Progress pg)
+ throws IOException {
+ return super.makeParagraphs(support, source, content, html, pg);
+ }
+
+ @Override
+ // and make it public!
+ public Paragraph processPara(String line, boolean html) {
+ return super.processPara(line, html);
+ }
+
+ @Override
+ // and make it public!
+ public List<Paragraph> requotify(Paragraph para, boolean html) {
+ return super.requotify(para, html);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.Main;
+import be.nikiroo.fanfix.output.BasicOutput;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.TraceHandler;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class ConversionTest extends TestLauncher {
+ private class MainWithConvert extends Main {
+ @Override
+ public int convert(String urlString, OutputType type, String target,
+ boolean infoCover, Progress pg) {
+ return super.convert(urlString, type, target, infoCover, pg);
+ }
+ }
+
+ private String testUri;
+ private String expectedDir;
+ private String resultDir;
+ private List<BasicOutput.OutputType> realTypes;
+ private Map<String, List<String>> skipCompare;
+ private Map<String, List<String>> skipCompareCross;
+
+ public ConversionTest(String testName, final String testUri,
+ final String expectedDir, final String resultDir, String[] args) {
+ super("Conversion - " + testName, args);
+
+ this.testUri = testUri;
+ this.expectedDir = expectedDir;
+ this.resultDir = resultDir;
+
+ // Special mode SYSOUT is not a file type (System.out)
+ realTypes = new ArrayList<BasicOutput.OutputType>();
+ for (BasicOutput.OutputType type : BasicOutput.OutputType.values()) {
+ if (!BasicOutput.OutputType.SYSOUT.equals(type)) {
+ realTypes.add(type);
+ }
+ }
+
+ if (!testUri.startsWith("http://") && !testUri.startsWith("https://")) {
+ addTest(new TestCase("Read the test file") {
+ @Override
+ public void test() throws Exception {
+ assertEquals("The test file \"" + testUri
+ + "\" cannot be found", true,
+ new File(testUri).exists());
+ }
+ });
+ }
+
+ addTest(new TestCase("Assure directories exist") {
+ @Override
+ public void test() throws Exception {
+ new File(expectedDir).mkdirs();
+ new File(resultDir).mkdirs();
+ assertEquals("The Expected directory \"" + expectedDir
+ + "\" cannot be created", true,
+ new File(expectedDir).exists());
+ assertEquals("The Result directory \"" + resultDir
+ + "\" cannot be created", true,
+ new File(resultDir).exists());
+ }
+ });
+
+ for (BasicOutput.OutputType type : realTypes) {
+ addTest(getTestFor(type));
+ }
+ }
+
+ @Override
+ protected void start() throws Exception {
+ skipCompare = new HashMap<String, List<String>>();
+ skipCompareCross = new HashMap<String, List<String>>();
+
+ skipCompare.put("epb.ncx", Arrays.asList(
+ " <meta name=\"dtb:uid\" content=",
+ " <meta name=\"epub-creator\" content=\""));
+ skipCompare.put("epb.opf", Arrays.asList(" <dc:subject>",
+ " <dc:identifier id=\"BookId\" opf:scheme=\"URI\">"));
+ skipCompare.put(".info", Arrays.asList("CREATION_DATE=",
+ "URL=\"file:/", "UUID=EPUBCREATOR=\"", ""));
+ skipCompare.put("URL", Arrays.asList("file:/"));
+
+ for (String key : skipCompare.keySet()) {
+ skipCompareCross.put(key, skipCompare.get(key));
+ }
+
+ skipCompareCross.put(".info", Arrays.asList(""));
+ skipCompareCross.put("epb.opf", Arrays.asList(" <dc:"));
+ skipCompareCross.put("title.xhtml",
+ Arrays.asList(" <div class=\"type\">"));
+ skipCompareCross.put("index.html",
+ Arrays.asList(" <div class=\"type\">"));
+ skipCompareCross.put("URL", Arrays.asList(""));
+ }
+
+ @Override
+ protected void stop() throws Exception {
+ }
+
+ private TestCase getTestFor(final BasicOutput.OutputType type) {
+ return new TestCase(type + " output mode") {
+ @Override
+ public void test() throws Exception {
+ File target = generate(this, testUri, new File(resultDir), type);
+ target = new File(target.getAbsolutePath()
+ + type.getDefaultExtension(false));
+
+ // Check conversion:
+ compareFiles(this, new File(expectedDir), new File(resultDir),
+ type, "Generate " + type);
+
+ // LATEX not supported as input
+ if (BasicOutput.OutputType.LATEX.equals(type)) {
+ return;
+ }
+
+ // Cross-checks:
+ for (BasicOutput.OutputType crossType : realTypes) {
+ File crossDir = Test.tempFiles
+ .createTempDir("cross-result");
+
+ generate(this, target.getAbsolutePath(), crossDir,
+ crossType);
+ compareFiles(this, new File(resultDir), crossDir,
+ crossType, "Cross compare " + crossType
+ + " generated from " + type);
+ }
+ }
+ };
+ }
+
+ private File generate(TestCase testCase, String testUri, File resultDir,
+ BasicOutput.OutputType type) throws Exception {
+ final List<String> errors = new ArrayList<String>();
+
+ TraceHandler previousTraceHandler = Instance.getInstance().getTraceHandler();
+ Instance.getInstance().setTraceHandler(new TraceHandler(true, true, 0) {
+ @Override
+ public void error(String message) {
+ errors.add(message);
+ }
+
+ @Override
+ public void error(Exception e) {
+ error(" ");
+ for (Throwable t = e; t != null; t = t.getCause()) {
+ error(((t == e) ? "(" : "..caused by: (")
+ + t.getClass().getSimpleName() + ") "
+ + t.getMessage());
+ for (StackTraceElement s : t.getStackTrace()) {
+ error("\t" + s.toString());
+ }
+ }
+ }
+ });
+
+ try {
+ File target = new File(resultDir, type.toString());
+ int code = new MainWithConvert().convert(testUri, type,
+ target.getAbsolutePath(), false, null);
+
+ String error = "";
+ for (String err : errors) {
+ if (!error.isEmpty())
+ error += "\n";
+ error += err;
+ }
+ testCase.assertEquals("The conversion returned an error message: "
+ + error, 0, errors.size());
+ if (code != 0) {
+ testCase.fail("The conversion failed with return code: " + code);
+ }
+
+ return target;
+ } finally {
+ Instance.getInstance().setTraceHandler(previousTraceHandler);
+ }
+ }
+
+ private void compareFiles(TestCase testCase, File expectedDir,
+ File resultDir, final BasicOutput.OutputType limitTiFiles,
+ final String errMess) throws Exception {
+
+ Map<String, List<String>> skipCompare = errMess.startsWith("Cross") ? this.skipCompareCross
+ : this.skipCompare;
+
+ FilenameFilter filter = null;
+ if (limitTiFiles != null) {
+ filter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().startsWith(
+ limitTiFiles.toString().toLowerCase());
+ }
+ };
+ }
+
+ List<String> resultFiles;
+ List<String> expectedFiles;
+ {
+ String[] resultArr = resultDir.list(filter);
+ Arrays.sort(resultArr);
+ resultFiles = Arrays.asList(resultArr);
+ String[] expectedArr = expectedDir.list(filter);
+ Arrays.sort(expectedArr);
+ expectedFiles = Arrays.asList(expectedArr);
+ }
+
+ testCase.assertEquals(errMess, expectedFiles, resultFiles);
+
+ for (int i = 0; i < resultFiles.size(); i++) {
+ File expected = new File(expectedDir, expectedFiles.get(i));
+ File result = new File(resultDir, resultFiles.get(i));
+
+ testCase.assertEquals(errMess + ": type mismatch: expected a "
+ + (expected.isDirectory() ? "directory" : "file")
+ + ", received a "
+ + (result.isDirectory() ? "directory" : "file"),
+ expected.isDirectory(), result.isDirectory());
+
+ if (expected.isDirectory()) {
+ compareFiles(testCase, expected, result, null, errMess);
+ continue;
+ }
+
+ if (expected.getName().endsWith(".cbz")
+ || expected.getName().endsWith(".epub")) {
+ File tmpExpected = Test.tempFiles.createTempDir(expected
+ .getName() + "[zip-content]");
+ File tmpResult = Test.tempFiles.createTempDir(result.getName()
+ + "[zip-content]");
+ IOUtils.unzip(expected, tmpExpected);
+ IOUtils.unzip(result, tmpResult);
+ compareFiles(testCase, tmpExpected, tmpResult, null, errMess);
+ } else {
+ List<String> expectedLines = Arrays.asList(IOUtils
+ .readSmallFile(expected).split("\n"));
+ List<String> resultLines = Arrays.asList(IOUtils.readSmallFile(
+ result).split("\n"));
+
+ String name = expected.getAbsolutePath();
+ if (name.startsWith(expectedDir.getAbsolutePath())) {
+ name = expectedDir.getName()
+ + name.substring(expectedDir.getAbsolutePath()
+ .length());
+ }
+
+ testCase.assertEquals(errMess + ": " + name
+ + ": the number of lines is not the same",
+ expectedLines.size(), resultLines.size());
+
+ for (int j = 0; j < expectedLines.size(); j++) {
+ String expectedLine = expectedLines.get(j);
+ String resultLine = resultLines.get(j);
+
+ boolean skip = false;
+ for (Entry<String, List<String>> skipThose : skipCompare
+ .entrySet()) {
+ for (String skipStart : skipThose.getValue()) {
+ if (name.endsWith(skipThose.getKey())
+ && expectedLine.startsWith(skipStart)
+ && resultLine.startsWith(skipStart)) {
+ skip = true;
+ }
+ }
+ }
+
+ if (skip) {
+ continue;
+ }
+
+ testCase.assertEquals(errMess + ": line " + (j + 1)
+ + " is not the same in file " + name, expectedLine,
+ resultLine);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.library.BasicLibrary;
+import be.nikiroo.fanfix.library.LocalLibrary;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class LibraryTest extends TestLauncher {
+ private BasicLibrary lib;
+ private File tmp;
+
+ public LibraryTest(String[] args) {
+ super("Library", args);
+
+ final String luid1 = "001"; // A
+ final String luid2 = "002"; // B
+ final String luid3 = "003"; // B then A, then B
+ final String name1 = "My story 1";
+ final String name2 = "My story 2";
+ final String name3 = "My story 3";
+ final String name3ex = "My story 3 [edited]";
+ final String source1 = "Source A";
+ final String source2 = "Source B";
+ final String author1 = "Unknown author";
+ final String author2 = "Another other otter author";
+
+ final String errMess = "The resulting stories in the list are not what we expected";
+
+ addSeries(new TestLauncher("Local", args) {
+ {
+ addTest(new TestCase("getList") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = lib.getList().getMetas();
+ assertEquals(errMess, Arrays.asList(),
+ titlesAsList(metas));
+ }
+ });
+
+ addTest(new TestCase("save") {
+ @Override
+ public void test() throws Exception {
+ lib.save(story(luid1, name1, source1, author1), luid1,
+ null);
+
+ List<MetaData> metas = lib.getList().getMetas();
+ assertEquals(errMess, Arrays.asList(name1),
+ titlesAsList(metas));
+ }
+ });
+
+ addTest(new TestCase("save more") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = null;
+
+ lib.save(story(luid2, name2, source2, author1), luid2,
+ null);
+
+ metas = lib.getList().getMetas();
+ assertEquals(errMess, Arrays.asList(name1, name2),
+ titlesAsList(metas));
+
+ lib.save(story(luid3, name3, source2, author1), luid3,
+ null);
+
+ metas = lib.getList().getMetas();
+ assertEquals(errMess,
+ Arrays.asList(name1, name2, name3),
+ titlesAsList(metas));
+ }
+ });
+
+ addTest(new TestCase("save override luid (change author)") {
+ @Override
+ public void test() throws Exception {
+ // same luid as a previous one
+ lib.save(story(luid3, name3ex, source2, author2),
+ luid3, null);
+
+ List<MetaData> metas = lib.getList().getMetas();
+ assertEquals(errMess,
+ Arrays.asList(name1, name2, name3ex),
+ titlesAsList(metas));
+ }
+ });
+
+ addTest(new TestCase("getList with results") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = lib.getList().getMetas();
+ assertEquals(3, metas.size());
+ }
+ });
+
+ addTest(new TestCase("getList by source") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = null;
+
+ metas = lib.getList().filter(source1, null, null);
+ assertEquals(1, metas.size());
+
+ metas = lib.getList().filter(source2, null, null);
+ assertEquals(2, metas.size());
+
+ metas = lib.getList().filter((String)null, null, null);
+ assertEquals(3, metas.size());
+ }
+ });
+
+ addTest(new TestCase("getList by author") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = null;
+
+ metas = lib.getList().filter(null, author1, null);
+ assertEquals(2, metas.size());
+
+ metas = lib.getList().filter(null, author2, null);
+ assertEquals(1, metas.size());
+
+ metas = lib.getList().filter((String)null, null, null);
+ assertEquals(3, metas.size());
+ }
+ });
+
+ addTest(new TestCase("changeType") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = null;
+
+ lib.changeSource(luid3, source1, null);
+
+ metas = lib.getList().filter(source1, null, null);
+ assertEquals(2, metas.size());
+
+ metas = lib.getList().filter(source2, null, null);
+ assertEquals(1, metas.size());
+
+ metas = lib.getList().filter((String)null, null, null);
+ assertEquals(3, metas.size());
+ }
+ });
+
+ addTest(new TestCase("save override luid (change source)") {
+ @Override
+ public void test() throws Exception {
+ List<MetaData> metas = null;
+
+ // same luid as a previous one
+ lib.save(story(luid3, "My story 3", source2, author2),
+ luid3, null);
+
+ metas = lib.getList().filter(source1, null, null);
+ assertEquals(1, metas.size());
+
+ metas = lib.getList().filter(source2, null, null);
+ assertEquals(2, metas.size());
+
+ metas = lib.getList().filter((String)null, null, null);
+ assertEquals(3, metas.size());
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ protected void start() throws Exception {
+ tmp = File.createTempFile(".test-fanfix", ".library");
+ tmp.delete();
+ tmp.mkdir();
+
+ lib = new LocalLibrary(tmp, OutputType.INFO_TEXT, OutputType.CBZ);
+ }
+
+ @Override
+ protected void stop() throws Exception {
+ IOUtils.deltree(tmp);
+ }
+
+ /**
+ * Return the (sorted) list of titles present in this list of
+ * {@link MetaData}s.
+ *
+ * @param metas
+ * the meta
+ *
+ * @return the sorted list
+ */
+ private List<String> titlesAsList(List<MetaData> metas) {
+ List<String> list = new ArrayList<String>();
+ for (MetaData meta : metas) {
+ list.add(meta.getTitle());
+ }
+
+ Collections.sort(list);
+ return list;
+ }
+
+ private Story story(String luid, String title, String source, String author) {
+ Story story = new Story();
+
+ MetaData meta = new MetaData();
+ meta.setLuid(luid);
+ meta.setTitle(title);
+ meta.setSource(source);
+ meta.setAuthor(author);
+ story.setMeta(meta);
+
+ Chapter resume = chapter(0, "Resume");
+ meta.setResume(resume);
+
+ List<Chapter> chapters = new ArrayList<Chapter>();
+ chapters.add(chapter(1, "Chap 1"));
+ chapters.add(chapter(2, "Chap 2"));
+ story.setChapters(chapters);
+
+ long words = 0;
+ for (Chapter chap : story.getChapters()) {
+ words += chap.getWords();
+ }
+ meta.setWords(words);
+
+ return story;
+ }
+
+ private Chapter chapter(int number, String name) {
+ Chapter chapter = new Chapter(number, name);
+
+ List<Paragraph> paragraphs = new ArrayList<Paragraph>();
+ paragraphs.add(new Paragraph(Paragraph.ParagraphType.NORMAL,
+ "some words in this paragraph please thank you", 8));
+
+ chapter.setParagraphs(paragraphs);
+
+ long words = 0;
+ for (Paragraph para : chapter.getParagraphs()) {
+ words += para.getWords();
+ }
+ chapter.setWords(words);
+
+ return chapter;
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.Config;
+import be.nikiroo.fanfix.bundles.ConfigBundle;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.TempFiles;
+import be.nikiroo.utils.resources.Bundles;
+import be.nikiroo.utils.test.TestLauncher;
+
+/**
+ * Tests for Fanfix.
+ *
+ * @author niki
+ */
+public class Test extends TestLauncher {
+ //
+ // 4 files can control the test:
+ // - test/VERBOSE: enable verbose mode
+ // - test/OFFLINE: to forbid any downloading
+ // - test/URLS: to allow testing URLs
+ // - test/FORCE_REFRESH: to force a clear of the cache
+ //
+ // Note that test/CACHE can be kept, as it will contain all internet related
+ // files you need (if you allow URLs, run the test once which will populate
+ // the CACHE then go OFFLINE, it will still work).
+ //
+ // The test files will be:
+ // - test/*.url: URL to download in text format, content = URL
+ // - test/*.story: text mode story, content = story
+ //
+
+ /**
+ * The temporary files handler.
+ */
+ static TempFiles tempFiles;
+
+ /**
+ * Create the Fanfix {@link TestLauncher}.
+ *
+ * @param args
+ * the arguments to configure the number of columns and the ok/ko
+ * {@link String}s
+ * @param urlsAllowed
+ * allow testing URLs (<tt>.url</tt> files)
+ *
+ * @throws IOException
+ */
+ public Test(String[] args, boolean urlsAllowed) throws IOException {
+ super("Fanfix", args);
+ Instance.getInstance().setTraceHandler(null);
+ addSeries(new BasicSupportUtilitiesTest(args));
+ addSeries(new BasicSupportDeprecatedTest(args));
+ addSeries(new LibraryTest(args));
+
+ File sources = new File("test/");
+ if (sources.isDirectory()) {
+ for (File file : sources.listFiles()) {
+ if (file.isDirectory()) {
+ continue;
+ }
+
+ String expectedDir = new File(file.getParentFile(), "expected_"
+ + file.getName()).getAbsolutePath();
+ String resultDir = new File(file.getParentFile(), "result_"
+ + file.getName()).getAbsolutePath();
+
+ String uri;
+ if (urlsAllowed && file.getName().endsWith(".url")) {
+ uri = IOUtils.readSmallFile(file).trim();
+ } else if (file.getName().endsWith(".story")) {
+ uri = file.getAbsolutePath();
+ } else {
+ continue;
+ }
+
+ addSeries(new ConversionTest(file.getName(), uri, expectedDir,
+ resultDir, args));
+ }
+ }
+ }
+
+ /**
+ * Main entry point of the program.
+ *
+ * @param args
+ * the arguments passed to the {@link TestLauncher}s.
+ * @throws IOException
+ * in case of I/O error
+ */
+ static public void main(String[] args) throws IOException {
+ Instance.init();
+
+ // Verbose mode:
+ boolean verbose = new File("test/VERBOSE").exists();
+
+ // Can force refresh
+ boolean forceRefresh = new File("test/FORCE_REFRESH").exists();
+
+ // Allow URLs:
+ boolean urlsAllowed = new File("test/URLS").exists();
+
+
+ // Only download files if allowed:
+ boolean offline = new File("test/OFFLINE").exists();
+ Instance.getInstance().getCache().setOffline(offline);
+
+
+
+ int result = 0;
+ tempFiles = new TempFiles("fanfix-test");
+ try {
+ File tmpConfig = tempFiles.createTempDir("fanfix-config");
+ File localCache = new File("test/CACHE");
+ prepareCache(localCache, forceRefresh);
+
+ ConfigBundle config = new ConfigBundle();
+ Bundles.setDirectory(tmpConfig.getAbsolutePath());
+ config.setString(Config.CACHE_DIR, localCache.getAbsolutePath());
+ config.setInteger(Config.CACHE_MAX_TIME_STABLE, -1);
+ config.setInteger(Config.CACHE_MAX_TIME_CHANGING, -1);
+ config.updateFile(tmpConfig.getPath());
+ System.setProperty("CONFIG_DIR", tmpConfig.getAbsolutePath());
+
+ Instance.init(true);
+ Instance.getInstance().getCache().setOffline(offline);
+
+ TestLauncher tests = new Test(args, urlsAllowed);
+ tests.setDetails(verbose);
+
+ result = tests.launch();
+
+ IOUtils.deltree(tmpConfig);
+ prepareCache(localCache, forceRefresh);
+ } finally {
+ // Test temp files
+ tempFiles.close();
+
+ // This is usually done in Fanfix.Main:
+ Instance.getInstance().getTempFiles().close();
+ }
+
+ System.exit(result);
+ }
+
+ /**
+ * Prepare the cache (or clean it up).
+ * <p>
+ * The cache directory will always exist if this method succeed
+ *
+ * @param localCache
+ * the cache directory
+ * @param forceRefresh
+ * TRUE to force acache refresh (delete all files)
+ *
+ * @throw IOException if the cache cannot be created
+ */
+ private static void prepareCache(File localCache, boolean forceRefresh)
+ throws IOException {
+ // if needed
+ localCache.mkdirs();
+
+ if (!localCache.isDirectory()) {
+ throw new IOException("Cannot get a cache");
+ }
+
+ // delete local cached files (_*) or all files if forceRefresh
+ for (File f : localCache.listFiles()) {
+ if (forceRefresh || f.getName().startsWith("_")) {
+ IOUtils.deltree(f);
+ }
+ }
+ }
+}
--- /dev/null
+TITLE="The trials of Fanfan"
+AUTHOR="UnknownArtist366"
+DATE="2018"
+SUBJECT="test"
+SOURCE="text"
+URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+TAGS=""
+UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+LUID=""
+LANG="en"
+IMAGES_DOCUMENT="false"
+TYPE="html"
+COVER=""
+EPUBCREATOR="Fanfix (by Niki)"
+PUBLISHER=""
+WORDCOUNT="57"
+CREATION_DATE="2018-03-28 08:40:18"
+FAKE_COVER="false"
--- /dev/null
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+——————————————————————
+
+This ‘story’ is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+—————————————————
+
+“Yes, quotes!”, I said.
+‘Those can start with a single- or double quote sign’, I continued.
+“They also supports other characters, and an optionnal leading dash.”
+“The optionnal leading dash is enough to signify “quote”.”
+
+Chapter 2: “Quote” test
+———————————————————————
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv='content-type' content='text/html; charset=utf-8'>
+ <meta name='viewport' content='width=device-width, initial-scale=1.0'>
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <title>The trials of Fanfan</title>
+</head>
+<body>
+
+ <div class="titlepage">
+ <h1>The trials of Fanfan</h1>
+ <div class="type"></div>
+ <div class="cover">
+ <img src="cover.png"></img>
+ </div>
+ <div class="author">UnknownArtist366</div>
+ </div>
+ <hr/><br/> <div class='normals'>
+ <span class='normal'>This ‘story’ is nothing more than a test file to check</span>
+ <span class='normal'>that the program can convert it into different</span>
+ <span class='normal'>formats correctly.</span>
+ </div>
+
+ <br/>
+ <h2>
+ <span class='chap'>Chapter <span class='chapnumber'>1</span>:</span>
+ <span class='chaptitle'>Quotes</span>
+ </h2>
+
+ <div class='chapter_content'>
+ <div class='dialogues'>
+ <div class='dialogue'>— “Yes, quotes!”, I said.</div>
+ <div class='dialogue'>— ‘Those can start with a single- or double quote sign’, I continued.</div>
+ <div class='dialogue'>— “They also supports other characters, and an optionnal leading dash.”</div>
+ <div class='dialogue'>— “The optionnal leading dash is enough to signify “quote”.”</div>
+ </div>
+
+ </div>
+ <h2>
+ <span class='chap'>Chapter <span class='chapnumber'>2</span>:</span>
+ <span class='chaptitle'>“Quote” test</span>
+ </h2>
+
+ <div class='chapter_content'>
+ <div class='normals'>
+ <span class='normal'>This test was just for the chapter title.</span>
+ <span class='normal'>We can also check for breaks.</span>
+ </div>
+ <hr class='break'/>
+ <div class='normals'>
+ <span class='normal'>This was a break space.</span>
+ </div>
+
+ </div></body>
--- /dev/null
+html {
+ text-align: justify;
+ max-width: 800px;
+ margin: auto;
+}
+
+.titlepage {
+ padding-left: 10%;
+ padding-right: 10%;
+ width: 80%;
+}
+
+h1 {
+ padding-bottom: 0;
+ margin-bottom: 0;
+ text-align: left;
+}
+
+.type {
+ position: relative;
+ font-size: large;
+ color: #666666;
+ font-weight: bold;
+ padding-bottom: 10px;
+ text-align: left;
+}
+
+.cover, .page-image {
+ width: 100%;
+}
+
+.cover img {
+ height: 45%;
+ max-width: 100%;
+ margin: auto;
+}
+
+.author {
+ text-align: right;
+ font-size: large;
+ font-style: italic;
+}
+
+.book, .chapter_content {
+ NO_text-indent: 40px;
+ padding-top: 40px;
+ padding-left: 5%;
+ padding-right: 5%;
+ width: 90%;
+}
+
+h2 {
+ border: 1px solid black;
+ color: #222222;
+ padding-left: 10px;
+ padding-right: 10px;
+ display: block;
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+h2 .chap {
+ color: #000000;
+ font-size: large;
+ font-variant: small-caps;
+ display: block;
+}
+
+h2 .chap:first-letter {
+ font-weight: bold;
+}
+
+h2 .chapnumber {
+ color: #000000;
+ font-size: xx-large;
+}
+
+h2 .chaptitle {
+ color: #444444;
+ font-size: large;
+ font-style: italic;
+ padding-bottom: 5px;
+ text-align: right;
+ display: block;
+}
+
+.normals {
+}
+
+.normal {
+ /* Can be removed if you want a more "compact" view */
+ display: block;
+}
+
+.blank {
+ /* Can be removed if you want a more "compact" view */
+ height: 24px;
+ width: 100%;
+}
+
+hr.break {
+ /* Can be removed if you want a more "compact" view */
+ margin-top: 48px;
+ margin-bottom: 48px;
+}
+
+.dialogues {
+}
+
+.dialogue {
+ font-style: italic;
+}
--- /dev/null
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+
+This 'story' is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+
+"Yes, quotes!", I said.
+'Those can start with a single- or double quote sign', I continued.
+"They also supports other characters, and an optionnal leading dash."
+"The optionnal leading dash is enough to signify "quote"."
+
+Chapter 2: “Quote” test
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
--- /dev/null
+TITLE="The trials of Fanfan"
+AUTHOR="UnknownArtist366"
+DATE="2018"
+SUBJECT="test"
+SOURCE="text"
+URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+TAGS=""
+UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+LUID=""
+LANG="en"
+IMAGES_DOCUMENT="false"
+TYPE="info_text"
+COVER=""
+EPUBCREATOR="Fanfix (by Niki)"
+PUBLISHER=""
+WORDCOUNT="57"
+CREATION_DATE="2018-03-28 08:39:39"
+FAKE_COVER="false"
--- /dev/null
+%
+% This LaTeX document was auto-generated by Fanfic Reader, created by Niki.
+%
+
+\documentclass[a4paper]{book}
+\usepackage[english]{babel}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{lmodern}
+\newcommand{\br}{\vspace{10 mm}}
+\newcommand{\say}{--- \noindent\emph}
+\hyphenpenalty=1000
+\tolerance=5000
+\begin{document}
+\date{2018}
+\title{The trials of Fanfan}
+\author{UnknownArtist366}
+\maketitle
+
+
+
+\chapter{Quotes}
+
+\say{``Yes, quotes!'', I said.}
+
+\say{`Those can start with a single- or double quote sign', I continued.}
+
+\noindent{}
+\say{``They also supports other characters, and an optionnal leading dash.''}
+
+\noindent{}
+\say{``The optionnal leading dash is enough to signify ``quote''.''}
+
+\noindent{}
+
+
+\chapter{``Quote'' test}
+This test was just for the chapter title.
+We can also check for breaks.
+
+\br
+This was a break space.
+
+\end{document}
--- /dev/null
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+——————————————————————
+
+This ‘story’ is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+—————————————————
+
+“Yes, quotes!”, I said.
+‘Those can start with a single- or double quote sign’, I continued.
+“They also supports other characters, and an optionnal leading dash.”
+“The optionnal leading dash is enough to signify “quote”.”
+
+Chapter 2: “Quote” test
+———————————————————————
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
--- /dev/null
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+
+This 'story' is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+- "Yes, quotes!", I said.
+'Those can start with a single- or double quote sign', I continued.
+- «They also supports other characters, and an optionnal leading dash.»
+- The optionnal leading dash is enough to signify "quote".
+
+Chapter 2: "Quote" test
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
+