Merge branch 'subtree'
authorNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 15:13:15 +0000 (17:13 +0200)
committerNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 15:13:15 +0000 (17:13 +0200)
14 files changed:
1  2 
src/be/nikiroo/fanfix/Instance.java
src/be/nikiroo/fanfix/bundles/UiConfig.java
src/be/nikiroo/fanfix/output/Html.java
src/be/nikiroo/fanfix/output/LaTeX.java
src/be/nikiroo/fanfix/searchable/BasicSearchable.java
src/be/nikiroo/fanfix/supported/BasicSupport.java
src/be/nikiroo/fanfix/supported/BasicSupportPara.java
src/be/nikiroo/fanfix/supported/BasicSupport_Deprecated.java
src/be/nikiroo/fanfix/supported/Cbz.java
src/be/nikiroo/fanfix/supported/EHentai.java
src/be/nikiroo/fanfix/supported/Epub.java
src/be/nikiroo/fanfix/supported/Fanfiction.java
src/be/nikiroo/fanfix/supported/InfoReader.java
src/be/nikiroo/fanfix/supported/Text.java

index d0d1c84ab7643b65b34a91859a1b75ca59533091,5c00a0756e0aa2f484e2003135b9123d396257f0..5c00a0756e0aa2f484e2003135b9123d396257f0
@@@ -147,10 -147,7 +147,7 @@@ public class Instance 
                lib = createDefaultLibrary(remoteDir);
  
                // create cache and TMP
-               File tmp = getFile(Config.CACHE_DIR, new File(configDir, "tmp"));
-               if (!tmp.isAbsolute()) {
-                       tmp = new File(configDir, tmp.getPath());
-               }
+               File tmp = getFile(Config.CACHE_DIR, configDir, "tmp");
                Image.setTemporaryFilesRoot(new File(tmp.getParent(), "tmp.images"));
  
                String ua = config.getString(Config.NETWORK_USER_AGENT, "");
                cache.setTraceHandler(tracer);
  
                // readerTmp / coverDir
-               readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER, new File(configDir, "tmp-reader"));
-               coverDir = getFile(Config.DEFAULT_COVERS_DIR, new File(configDir, "covers"));
+               readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER, configDir, "tmp-reader");
+               coverDir = getFile(Config.DEFAULT_COVERS_DIR, configDir, "covers");
                coverDir.mkdirs();
  
                try {
                } else {
                        String libDir = System.getenv("BOOKS_DIR");
                        if (libDir == null || libDir.isEmpty()) {
-                               libDir = config.getString(Config.LIBRARY_DIR, "$HOME/Books");
-                               if (!getFile(libDir).isAbsolute()) {
-                                       libDir = new File(configDir, libDir).getPath();
-                               }
+                               libDir = getFile(Config.LIBRARY_DIR, configDir, "$HOME/Books").getPath();
                        }
                        try {
-                               lib = new LocalLibrary(getFile(libDir), config);
+                               lib = new LocalLibrary(new File(libDir), config);
                        } catch (Exception e) {
-                               tracer.error(new IOException("Cannot create library for directory: " + getFile(libDir), e));
+                               tracer.error(new IOException("Cannot create library for directory: " + libDir, e));
                        }
                }
  
         * Return a path, but support the special $HOME variable.
         * 
         * @param id  the key for the path, which may contain "$HOME"
-        * @param def the default value if none
+        * @param configDir the directory to use as base if not absolute
+        * @param def the default value if none (will be configDir-rooted if needed)
         * @return the path, with expanded "$HOME" if needed
         */
-       protected File getFile(Config id, File def) {
-               String path = config.getString(id, def.getPath());
-               return getFile(path);
+       protected File getFile(Config id, String configDir, String def) {
+               String path = config.getString(id, def);
+               return getFile(path, configDir);
        }
  
        /**
         * Return a path, but support the special $HOME variable.
         * 
         * @param id  the key for the path, which may contain "$HOME"
-        * @param def the default value if none
+        * @param configDir the directory to use as base if not absolute
+        * @param def the default value if none (will be configDir-rooted if needed)
         * @return the path, with expanded "$HOME" if needed
         */
-       protected File getFile(UiConfig id, File def) {
-               String path = uiconfig.getString(id, def.getPath());
-               return getFile(path);
+       protected File getFile(UiConfig id, String configDir, String def) {
+               String path = uiconfig.getString(id, def);
+               return getFile(path, configDir);
        }
  
        /**
         * Return a path, but support the special $HOME variable.
         * 
         * @param path the path, which may contain "$HOME"
+        * @param configDir the directory to use as base if not absolute
         * @return the path, with expanded "$HOME" if needed
         */
-       protected File getFile(String path) {
+       protected File getFile(String path, String configDir) {
                File file = null;
                if (path != null && !path.isEmpty()) {
                        path = path.replace('/', File.separatorChar);
                        if (path.contains("$HOME")) {
                                path = path.replace("$HOME", getHome());
+                       } else if (!path.startsWith("/")) {
+                               path = new File(configDir, path).getPath();
                        }
  
                        file = new File(path);
index 0f3142d3f5552833c162c846122061b9a347e9da,2122ccf8b48164ca04562227cc1c6d32a8388e4f..2122ccf8b48164ca04562227cc1c6d32a8388e4f
@@@ -31,6 -31,9 +31,9 @@@ public enum UiConfig 
        @Meta(description = "The external viewer for non-images documents (or empty to use the system default program for the given file type)",//
        format = Format.STRING)
        NON_IMAGES_DOCUMENT_READER, //
+       @Meta(description = "The icon to use for the program",//
+       format = Format.FIXED_LIST, def = "default", list = { "default", "alternative", "magic-book", "pony-book", "pony-library" })
+       PROGRAM_ICON, //
        //
        // GUI settings (hidden in config)
        //
index da79466a40d9cc2fcf80872d77ef6e588398e732,f81ea1dc5f643d20de832dcd96b48ec4070172ab..f81ea1dc5f643d20de832dcd96b48ec4070172ab
@@@ -216,7 -216,7 +216,7 @@@ class Html extends BasicOutput 
                        writer.write("                  <div class='dialogue'>&mdash; ");
                        break;
                case IMAGE:
-                       // TODO
+                       // TODO check if images work OK
                        writer.write("<a href='"
                                        + StringUtils.xmlEscapeQuote(para.getContent()) + "'>"
                                        + StringUtils.xmlEscape(para.getContent()) + "</a>");
index a15e67c900c7fe59dbbde4aa901c7bc246255e61,a406fc35fd425635baad02e6335de0cbaf0a9f61..a406fc35fd425635baad02e6335de0cbaf0a9f61
@@@ -149,7 -149,7 +149,7 @@@ class LaTeX extends BasicOutput 
                        lastWasQuote = true;
                        break;
                case IMAGE:
-                       // TODO
+                       // TODO what about images in LaTeX?
                        break;
                }
        }
index b943abd25401f426bad456140eea004be06437fb,cb0b1712ccb3fc7762354f799a04f65b45d89de8..cb0b1712ccb3fc7762354f799a04f65b45d89de8
@@@ -241,22 -241,22 +241,22 @@@ public abstract class BasicSearchable 
                if (type != null) {
                        switch (type) {
                        case FIMFICTION:
-                               // TODO
+                               // TODO searchable for FIMFICTION
                                break;
                        case FANFICTION:
                                support = new Fanfiction(type);
                                break;
                        case MANGAHUB:
-                               // TODO
+                               // TODO searchable for MANGAHUB
                                break;
                        case E621:
-                               // TODO
+                               // TODO searchable for E621
                                break;
                        case YIFFSTAR:
-                               // TODO
+                               // TODO searchable for YIFFSTAR
                                break;
                        case E_HENTAI:
-                               // TODO
+                               // TODO searchable for E_HENTAI
                                break;
                        case MANGA_LEL:
                                support = new MangaLel();
index 0a5ec3686e1be43c52a328d1a1be86e805ad21fe,f1635589bfd70b0dca80b5972dcfd241bda895f8..f1635589bfd70b0dca80b5972dcfd241bda895f8
@@@ -374,7 -374,25 +374,25 @@@ public abstract class BasicSupport 
                sourceNode = loadDocument(source);
  
                try {
-                       return doProcess(pg);
+                       Story story = doProcess(pg);
+                       
+                       // Check for "no chapters" stories
+                       if (story.getChapters().isEmpty()
+                                       && story.getMeta().getResume() != null
+                                       && !story.getMeta().getResume().getParagraphs().isEmpty()) {
+                               Chapter resume = story.getMeta().getResume();
+                               resume.setName("");
+                               resume.setNumber(1);
+                               story.getChapters().add(resume);
+                               story.getMeta().setWords(resume.getWords());
+                               String descChapterName = Instance.getInstance().getTrans()
+                                               .getString(StringId.DESCRIPTION);
+                               resume = new Chapter(0, descChapterName);
+                               story.getMeta().setResume(resume);
+                       }
+                       
+                       return story;
                } finally {
                        close();
                }
                story.setChapters(new ArrayList<Chapter>());
                List<Entry<String, URL>> chapters = getChapters(pgGetChapters);
                pgGetChapters.done(); // 20%
+               
                if (chapters != null) {
                        Progress pgChaps = new Progress("Extracting chapters", 0,
                                        chapters.size() * 300);
  
                                words += cc.getWords();
                                story.getChapters().add(cc);
-                               story.getMeta().setWords(words);
  
                                i++;
                        }
+                       
+                       story.getMeta().setWords(words);
  
                        pgChaps.setName("Extracting chapters");
                        pgChaps.done();
         *            the chapter name
         * @param content
         *            the content of the chapter
-        * @return the {@link Chapter}
+        *            
+        * @return the {@link Chapter}, never NULL
         * 
         * @throws IOException
         *             in case of I/O error
index 58c363af59b77e1b4f4d1c323fefe2af350ad31b,1dbedc9cc326b0bdcb18e11753c8d78ccc611e22..1dbedc9cc326b0bdcb18e11753c8d78ccc611e22
@@@ -60,7 -60,7 +60,7 @@@ public class BasicSupportPara 
         * @param html
         *            TRUE if the input content is in HTML mode
         * 
-        * @return the {@link Chapter}
+        * @return the {@link Chapter}, never NULL
         * 
         * @throws IOException
         *             in case of I/O error
         * @param html
         *            TRUE if the input content is in HTML mode
         * 
-        * @return the processed {@link Paragraph}
+        * @return the processed {@link Paragraph}, never NULL
         */
        protected Paragraph processPara(String line, boolean html) {
                if (html) {
         * @param pg
         *            the optional progress reporter
         * 
-        * @return the {@link Paragraph}s
+        * @return the {@link Paragraph}s (can be empty but never NULL)
         * 
         * @throws IOException
         *             in case of I/O error
         * @param html
         *            TRUE if the input content is in HTML mode
         * 
-        * @return the {@link Paragraph}
+        * @return the {@link Paragraph}, never NULL
         */
        protected Paragraph makeParagraph(BasicSupport support, URL source,
                        String line, boolean html) {
index bc3738a211bc22b92ac8811e85a549dd17777618,a50ee3cf48a962a38b5ebe52b33184c956169ffd..a50ee3cf48a962a38b5ebe52b33184c956169ffd
@@@ -317,7 -317,6 +317,6 @@@ public abstract class BasicSupport_Depr
  
                                                words += cc.getWords();
                                                story.getChapters().add(cc);
-                                               story.getMeta().setWords(words);
                                        } finally {
                                                if (chapIn != null) {
                                                        chapIn.close();
  
                                        i++;
                                }
+                               
+                               story.getMeta().setWords(words);
  
                                pgChaps.setName("Extracting chapters");
                        } else {
                                pg.setProgress(80);
                        }
  
-                       return story;
+                       // Check for "no chapters" stories
+                       if (story.getChapters().isEmpty()
+                                       && story.getMeta().getResume() != null
+                                       && !story.getMeta().getResume().getParagraphs().isEmpty()) {
+                               Chapter resume = story.getMeta().getResume();
+                               resume.setName("");
+                               resume.setNumber(1);
+                               story.getChapters().add(resume);
+                               story.getMeta().setWords(resume.getWords());
+                               String descChapterName = Instance.getInstance().getTrans()
+                                               .getString(StringId.DESCRIPTION);
+                               resume = new Chapter(0, descChapterName);
+                               story.getMeta().setResume(resume);
+                       }
  
+                       return story;
                } finally {
                        close();
  
         * @param pg
         *            the optional progress reporter
         * 
-        * @return the {@link Chapter}
+        * @return the {@link Chapter}, never NULL
         * 
         * @throws IOException
         *             in case of I/O error
         * @param pg
         *            the optional progress reporter
         * 
-        * @return the {@link Paragraph}s
+        * @return the {@link Paragraph}s (can be empty, but never NULL)
         * 
         * @throws IOException
         *             in case of I/O error
                }
  
                List<Paragraph> paras = new ArrayList<Paragraph>();
                if (content != null && !content.trim().isEmpty()) {
                        if (isHtml()) {
                                String[] tab = content.split("(<p>|</p>|<br>|<br/>)");
         * @param line
         *            the textual content of the paragraph
         * 
-        * @return the {@link Paragraph}
+        * @return the {@link Paragraph}, never NULL
         */
        private Paragraph makeParagraph(URL source, String line) {
                Image image = null;
         * @param line
         *            the raw line
         * 
-        * @return the processed {@link Paragraph}
+        * @return the processed {@link Paragraph}, never NULL
         */
        protected Paragraph processPara(String line) {
                line = ifUnhtml(line).trim();
index a6188ec55085467b1306f5a9ac91e0083bdf014d,7fe496d97ea8acb9e8869bb41c37d6723e19a636..7fe496d97ea8acb9e8869bb41c37d6723e19a636
@@@ -168,7 -168,7 +168,7 @@@ class Cbz extends Epub 
                        }
  
                        if (!imagesList.isEmpty()) {
-                               Chapter chap = new Chapter(story.getChapters().size() + 1, null);
+                               Chapter chap = new Chapter(story.getChapters().size() + 1, "");
                                story.getChapters().add(chap);
  
                                for (String uuid : imagesList) {
index 03c1557432194187cb54e2b43e9f4239d42c1668,3c734329e3bac03e456665958b6699cc623b3ff8..3c734329e3bac03e456665958b6699cc623b3ff8
@@@ -54,7 -54,7 +54,7 @@@ class EHentai extends BasicSupport_Depr
                // There is no chapters on e621, just pagination...
                Story story = super.process(url, pg);
  
-               Chapter only = new Chapter(1, null);
+               Chapter only = new Chapter(1, "");
                for (Chapter chap : story) {
                        only.getParagraphs().addAll(chap.getParagraphs());
                }
                        } else if (langLine.equalsIgnoreCase("French")) {
                                lang = "fr";
                        } else {
-                               // TODO find the code?
+                               // TODO find the code for other languages?
                                lang = langLine;
                        }
                }
index 90a9f458e4fd3ce9c0f8ad331bc407a27dc760ca,965a27affda88bfe8919e744b80962f58c3aa465..965a27affda88bfe8919e744b80962f58c3aa465
@@@ -7,6 -7,8 +7,8 @@@ import java.net.URISyntaxException
  import java.net.URL;
  import java.net.URLDecoder;
  import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Collections;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  
@@@ -107,6 -109,7 +109,7 @@@ class Epub extends InfoText 
                                if (!entry.isDirectory()
                                                && entry.getName().startsWith(getDataPrefix())) {
                                        String entryLName = entry.getName().toLowerCase();
+                                       entryLName = entryLName.substring(getDataPrefix().length());
  
                                        boolean imageEntry = false;
                                        for (String ext : bsImages.getImageExt(false)) {
                                                }
                                        }
  
-                                       if (entry.getName().equals(getDataPrefix() + "version")) {
+                                       if (entryLName.equals("version")) {
                                                // Nothing to do for now ("first"
                                                // version is 3.0)
                                        } else if (entryLName.endsWith(".info")) {
                                                IOUtils.write(zipIn, tmpInfo);
                                        } else if (imageEntry) {
                                                // Cover
-                                               if (getCover()) {
+                                               if (getCover() && cover == null) {
                                                        try {
                                                                cover = new Image(zipIn);
                                                        } catch (Exception e) {
                                                                                .error(e);
                                                        }
                                                }
-                                       } else if (entry.getName()
-                                                       .equals(getDataPrefix() + "URL")) {
+                                       } else if (entryLName.equals("url")) {
                                                String[] descArray = StringUtils
                                                                .unhtml(IOUtils.readSmallStream(zipIn)).trim()
                                                                .split("\n");
                                                if (descArray.length > 0) {
                                                        url = descArray[0].trim();
                                                }
-                                       } else if (entry.getName().endsWith(".desc")) {
+                                       } else if (entryLName.endsWith(".desc")) {
                                                // // For old files
                                                // if (this.desc != null) {
                                                // this.desc = IOUtils.readSmallStream(zipIn).trim();
                                                // }
-                                       } else if (entry.getName()
-                                                       .equals(getDataPrefix() + "SUMMARY")) {
+                                       } else if (entryLName.equals("summary")) {
                                                String[] descArray = StringUtils
                                                                .unhtml(IOUtils.readSmallStream(zipIn)).trim()
                                                                .split("\n");
                                }
                        }
  
-                       if (requireInfo() && (!tmp.exists() || !tmpInfo.exists())) {
+                       if (requireInfo() && !tmp.exists()) {
                                throw new IOException(
                                                "file not supported (maybe not created with this program or corrupt)");
                        }
                        } else {
                                if (title == null || title.isEmpty()) {
                                        title = getSourceFileOriginal().getName();
-                                       if (title.toLowerCase().endsWith(".cbz")) {
-                                               title = title.substring(0, title.length() - 4);
+                                       String exts[] = new String[] {".epub", ".cbz"};
+                                       for (String ext : exts) {
+                                               if (title.toLowerCase().endsWith(ext)) {
+                                                       title = title.substring(0,
+                                                                       title.length() - ext.length());
+                                               }
                                        }
                                        title = URLDecoder.decode(title, "UTF-8").trim();
                                }
  
                                meta = new MetaData();
                                meta.setLang("en");
-                               meta.setTags(new ArrayList<String>());
+                               meta.setTags(Arrays.asList("[no_info]"));
                                meta.setSource(getType().getSourceName());
                                meta.setUuid(url);
                                meta.setUrl(url);
                                meta.setTitle(title);
                                meta.setAuthor(author);
                                meta.setImageDocument(isImagesDocumentByDefault());
+                               
+                               InfoReader.completeMeta(tmp, meta);
                        }
  
                        if (meta.getCover() == null) {
index 282192e065bb82fd238c06b863d0e9a3647f4917,16b44f806de8b1df604a0f5e0637a017f39d8ce2..16b44f806de8b1df604a0f5e0637a017f39d8ce2
@@@ -45,7 -45,7 +45,7 @@@ class Fanfiction extends BasicSupport_D
                meta.setPublisher(getType().getSourceName());
                meta.setUuid(source.toString());
                meta.setLuid("");
-               meta.setLang("en"); // TODO!
+               meta.setLang("en"); // TODO find language of book
                meta.setSubject(getSubject(reset(in)));
                meta.setType(getType().toString());
                meta.setImageDocument(false);
index 206464f45a0a2e7c989e6fa6719afa22ca61c9cb,d5eeeb1d9190a022db4bf360cb243710decc493e..d5eeeb1d9190a022db4bf360cb243710decc493e
@@@ -48,7 -48,6 +48,6 @@@ public class InfoReader 
                                                meta.getUrl())) {
  
                                        // TODO: not nice, would be better to do it properly...
                                        String base = infoFile.getPath();
                                        if (base.endsWith(".info")) {
                                                base = base.substring(0,
                                                textFile = new File(base + ".text");
                                        }
  
-                                       if (textFile.exists()) {
-                                               final URL source = textFile.toURI().toURL();
-                                               final MetaData[] superMetaA = new MetaData[1];
-                                               @SuppressWarnings("unused")
-                                               Text unused = new Text() {
-                                                       private boolean loaded = loadDocument();
-                                                       @Override
-                                                       public SupportType getType() {
-                                                               return SupportType.TEXT;
-                                                       }
-                                                       protected boolean loadDocument()
-                                                                       throws IOException {
-                                                               loadDocument(source);
-                                                               superMetaA[0] = getMeta();
-                                                               return true;
-                                                       }
-                                                       @Override
-                                                       protected Image getCover(File sourceFile) {
-                                                               return null;
-                                                       }
-                                               };
-                                               MetaData superMeta = superMetaA[0];
-                                               if (!hasIt(meta.getTitle())) {
-                                                       meta.setTitle(superMeta.getTitle());
-                                               }
-                                               if (!hasIt(meta.getAuthor())) {
-                                                       meta.setAuthor(superMeta.getAuthor());
-                                               }
-                                               if (!hasIt(meta.getDate())) {
-                                                       meta.setDate(superMeta.getDate());
-                                               }
-                                               if (!hasIt(meta.getUrl())) {
-                                                       meta.setUrl(superMeta.getUrl());
-                                               }
-                                       }
+                                       completeMeta(textFile, meta);
+                                       //
                                }
  
                                return meta;
                                "File given as argument does not exists: "
                                                + infoFile.getAbsolutePath());
        }
+       
+       /**
+        * Complete the given {@link MetaData} with the original text file if needed
+        * and possible.
+        * 
+        * @param textFile
+        *            the original text file
+        * @param meta
+        *            the {@link MetaData} to complete if needed and possible
+        * 
+        * @throws IOException
+        *             in case of I/O errors
+        */
+       static public void completeMeta(File textFile,
+                       MetaData meta)  throws IOException {
+               if (textFile != null && textFile.exists()) {
+                       final URL source = textFile.toURI().toURL();
+                       final MetaData[] superMetaA = new MetaData[1];
+                       @SuppressWarnings("unused")
+                       Text unused = new Text() {
+                               private boolean loaded = loadDocument();
+                               @Override
+                               public SupportType getType() {
+                                       return SupportType.TEXT;
+                               }
+                               protected boolean loadDocument() throws IOException {
+                                       loadDocument(source);
+                                       superMetaA[0] = getMeta();
+                                       return true;
+                               }
+                               @Override
+                               protected Image getCover(File sourceFile) {
+                                       return null;
+                               }
+                       };
+                       MetaData superMeta = superMetaA[0];
+                       if (!hasIt(meta.getTitle())) {
+                               meta.setTitle(superMeta.getTitle());
+                       }
+                       if (!hasIt(meta.getAuthor())) {
+                               meta.setAuthor(superMeta.getAuthor());
+                       }
+                       if (!hasIt(meta.getDate())) {
+                               meta.setDate(superMeta.getDate());
+                       }
+                       if (!hasIt(meta.getUrl())) {
+                               meta.setUrl(superMeta.getUrl());
+                       }
+               }
+       }
  
        /**
         * Check if we have non-empty values for all the given {@link String}s.
index ade797fbf71ae3182a05ced4a6f0fdbb74b1f118,232eab6ec4f7736b6b470011f51d09548fe733e8..232eab6ec4f7736b6b470011f51d09548fe733e8
@@@ -102,7 -102,7 +102,7 @@@ class Text extends BasicSupport 
        }
  
        private String getLang() {
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
                scan.next(); // Title
        }
  
        private String getTitle() {
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
                return scan.next();
        }
  
        private String getAuthor() {
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
                scan.next();
        }
  
        private String getDate() {
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
                scan.next();
        protected List<Entry<String, URL>> getChapters(Progress pg)
                        throws IOException {
                List<Entry<String, URL>> chaps = new ArrayList<Entry<String, URL>>();
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
-               boolean prevLineEmpty = false;
+               String line = "first is not empty";
                while (scan.hasNext()) {
-                       String line = scan.next();
+                       boolean prevLineEmpty = line.trim().isEmpty();
+                       line = scan.next();
                        if (prevLineEmpty && detectChapter(line, chaps.size() + 1) != null) {
                                String chapName = Integer.toString(chaps.size() + 1);
                                int pos = line.indexOf(':');
                                                chapName, //
                                                getSourceFile().toURI().toURL()));
                        }
-                       prevLineEmpty = line.trim().isEmpty();
                }
+               
                return chaps;
        }
  
        protected String getChapterContent(URL source, int number, Progress pg)
                        throws IOException {
                StringBuilder builder = new StringBuilder();
-               @SuppressWarnings("resource")
+               @SuppressWarnings("resource") // cannot close, or we loose getInput()!
                Scanner scan = new Scanner(getInput(), "UTF-8");
                scan.useDelimiter("\\n");
-               boolean inChap = false;
+               scan.next(); // title
+               scan.next(); // author
+               scan.next(); // date or empty
+               Boolean inChap = null;
+               String line = "";
                while (scan.hasNext()) {
-                       String line = scan.next();
-                       if (!inChap && detectChapter(line, number) != null) {
+                       if (number == 0 && !line.trim().isEmpty()) {
+                               // We found pre-chapter content, we are checking for
+                               // Chapter 0 (fake chapter) --> keep the content
+                               if (inChap == null)
+                                       inChap = true;
+                       }
+                       line = scan.next();
+                       if ((inChap == null || !inChap) && detectChapter(line, number) != null) {
                                inChap = true;
                        } else if (detectChapter(line, number + 1) != null) {
                                break;
-                       } else if (inChap) {
+                       } else if (inChap != null && inChap) {
                                builder.append(line);
                                builder.append("\n");
                        }