1 package be
.nikiroo
.fanfix
.supported
;
4 import java
.io
.FileInputStream
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
7 import java
.net
.URISyntaxException
;
9 import java
.util
.AbstractMap
;
10 import java
.util
.ArrayList
;
11 import java
.util
.List
;
12 import java
.util
.Map
.Entry
;
13 import java
.util
.Scanner
;
15 import org
.jsoup
.nodes
.Document
;
17 import be
.nikiroo
.fanfix
.Instance
;
18 import be
.nikiroo
.fanfix
.bundles
.Config
;
19 import be
.nikiroo
.fanfix
.data
.MetaData
;
20 import be
.nikiroo
.utils
.Image
;
21 import be
.nikiroo
.utils
.ImageUtils
;
22 import be
.nikiroo
.utils
.MarkableFileInputStream
;
23 import be
.nikiroo
.utils
.Progress
;
26 * Support class for local stories encoded in textual format, with a few rules:
28 * <li>The title must be on the first line</li>
29 * <li>The author (preceded by nothing, "by " or "©") must be on the second
30 * line, possibly with the publication date in parenthesis (i.e., "
31 * <tt>By Unknown (3rd October 1998)</tt>")</li>
32 * <li>Chapters must be declared with "<tt>Chapter x</tt>" or "
33 * <tt>Chapter x: NAME OF THE CHAPTER</tt>", where "<tt>x</tt>" is the chapter
35 * <li>A description of the story must be given as chapter number 0</li>
36 * <li>A cover may be present, with the same filename but a PNG, JPEG or JPG
42 class Text
extends BasicSupport
{
43 private File sourceFile
;
44 private InputStream in
;
46 protected File
getSourceFile() {
50 protected InputStream
getInput() {
54 } catch (IOException e
) {
55 Instance
.getTraceHandler().error(
56 new IOException("Cannot reset the Text stream", e
));
66 protected boolean isHtml() {
71 public String
getSourceName() {
76 protected Document
loadDocument(URL source
) throws IOException
{
78 sourceFile
= new File(source
.toURI());
79 in
= new MarkableFileInputStream(new FileInputStream(sourceFile
));
80 } catch (URISyntaxException e
) {
81 throw new IOException("Cannot load the text document: " + source
);
88 protected MetaData
getMeta() throws IOException
{
89 MetaData meta
= new MetaData();
91 meta
.setTitle(getTitle());
92 meta
.setAuthor(getAuthor());
93 meta
.setDate(getDate());
94 meta
.setTags(new ArrayList
<String
>());
95 meta
.setSource(getSourceName());
96 meta
.setUrl(getSourceFile().toURI().toURL().toString());
97 meta
.setPublisher("");
98 meta
.setUuid(getSourceFile().toString());
100 meta
.setLang(getLang()); // default is EN
101 meta
.setSubject(getSourceFile().getParentFile().getName());
102 meta
.setType(getType().toString());
103 meta
.setImageDocument(false);
104 meta
.setCover(getCover(getSourceFile()));
109 private String
getLang() {
110 @SuppressWarnings("resource")
111 Scanner scan
= new Scanner(getInput(), "UTF-8");
112 scan
.useDelimiter("\\n");
113 scan
.next(); // Title
114 scan
.next(); // Author (Date)
115 String chapter0
= scan
.next(); // empty or Chapter 0
116 while (chapter0
.isEmpty()) {
117 chapter0
= scan
.next();
120 String lang
= detectChapter(chapter0
, 0);
123 lang
= detectChapter(chapter0
, 1);
129 lang
= lang
.toLowerCase();
135 private String
getTitle() {
136 @SuppressWarnings("resource")
137 Scanner scan
= new Scanner(getInput(), "UTF-8");
138 scan
.useDelimiter("\\n");
142 private String
getAuthor() {
143 @SuppressWarnings("resource")
144 Scanner scan
= new Scanner(getInput(), "UTF-8");
145 scan
.useDelimiter("\\n");
147 String authorDate
= scan
.next();
149 String author
= authorDate
;
150 int pos
= authorDate
.indexOf('(');
152 author
= authorDate
.substring(0, pos
);
155 return BasicSupportHelper
.fixAuthor(author
);
158 private String
getDate() {
159 @SuppressWarnings("resource")
160 Scanner scan
= new Scanner(getInput(), "UTF-8");
161 scan
.useDelimiter("\\n");
163 String authorDate
= scan
.next();
166 int pos
= authorDate
.indexOf('(');
168 date
= authorDate
.substring(pos
+ 1).trim();
169 pos
= date
.lastIndexOf(')');
171 date
= date
.substring(0, pos
).trim();
179 protected String
getDesc() throws IOException
{
180 return getChapterContent(null, 0, null);
183 private Image
getCover(File sourceFile
) {
184 String path
= sourceFile
.getName();
186 for (String ext
: new String
[] { ".txt", ".text", ".story" }) {
187 if (path
.endsWith(ext
)) {
188 path
= path
.substring(0, path
.length() - ext
.length());
192 Image cover
= BasicSupportImages
.getImage(this,
193 sourceFile
.getParentFile(), path
);
196 File tmp
= Instance
.getTempFiles().createTempFile(
198 ImageUtils
.getInstance().saveAsImage(cover
, tmp
, "png");
200 } catch (IOException e
) {
209 protected List
<Entry
<String
, URL
>> getChapters(Progress pg
)
211 List
<Entry
<String
, URL
>> chaps
= new ArrayList
<Entry
<String
, URL
>>();
212 @SuppressWarnings("resource")
213 Scanner scan
= new Scanner(getInput(), "UTF-8");
214 scan
.useDelimiter("\\n");
215 boolean prevLineEmpty
= false;
216 while (scan
.hasNext()) {
217 String line
= scan
.next();
218 if (prevLineEmpty
&& detectChapter(line
, chaps
.size() + 1) != null) {
219 String chapName
= Integer
.toString(chaps
.size() + 1);
220 int pos
= line
.indexOf(':');
221 if (pos
>= 0 && pos
+ 1 < line
.length()) {
222 chapName
= line
.substring(pos
+ 1).trim();
225 chaps
.add(new AbstractMap
.SimpleEntry
<String
, URL
>(//
227 getSourceFile().toURI().toURL()));
230 prevLineEmpty
= line
.trim().isEmpty();
237 protected String
getChapterContent(URL source
, int number
, Progress pg
)
239 StringBuilder builder
= new StringBuilder();
240 @SuppressWarnings("resource")
241 Scanner scan
= new Scanner(getInput(), "UTF-8");
242 scan
.useDelimiter("\\n");
243 boolean inChap
= false;
244 while (scan
.hasNext()) {
245 String line
= scan
.next();
246 if (detectChapter(line
, number
) != null) {
248 } else if (inChap
&& detectChapter(line
, number
+ 1) != null) {
251 builder
.append(line
);
252 builder
.append("\n");
256 return builder
.toString();
260 protected void close() {
261 InputStream in
= getInput();
265 } catch (IOException e
) {
266 Instance
.getTraceHandler().error(
268 "Cannot close the text source file input", e
));
276 protected boolean supports(URL url
) {
277 return supports(url
, false);
281 * Check if we supports this {@link URL}, that is, if the info file can be
282 * found OR not found.
285 * the {@link URL} to check
287 * TRUE to require the info file, FALSE to forbid the info file
289 * @return TRUE if it is supported
291 protected boolean supports(URL url
, boolean info
) {
292 boolean infoPresent
= false;
293 if ("file".equals(url
.getProtocol())) {
296 file
= new File(url
.toURI());
297 file
= assureNoTxt(file
);
298 file
= new File(file
.getPath() + ".info");
299 } catch (URISyntaxException e
) {
300 Instance
.getTraceHandler().error(e
);
304 infoPresent
= (file
!= null && file
.exists());
307 return infoPresent
== info
;
311 * Remove the ".txt" extension if it is present.
314 * the file to process
316 * @return the same file or a copy of it without the ".txt" extension if it
319 protected File
assureNoTxt(File file
) {
320 if (file
.getName().endsWith(".txt")) {
321 file
= new File(file
.getPath().substring(0,
322 file
.getPath().length() - 4));
329 * Check if the given line looks like the given starting chapter in a
330 * supported language, and return the language if it does (or NULL if not).
335 * @return the language or NULL
337 static private String
detectChapter(String line
, int number
) {
338 line
= line
.toUpperCase();
339 for (String lang
: Instance
.getConfig().getString(Config
.CHAPTER
)
341 String chapter
= Instance
.getConfig().getStringX(Config
.CHAPTER
,
343 if (chapter
!= null && !chapter
.isEmpty()) {
344 chapter
= chapter
.toUpperCase() + " ";
345 if (line
.startsWith(chapter
)) {
346 // We want "[CHAPTER] [number]: [name]", with ": [name]"
348 String test
= line
.substring(chapter
.length()).trim();
349 if (test
.startsWith(Integer
.toString(number
))) {
351 .substring(Integer
.toString(number
).length())
353 if (test
.isEmpty() || test
.startsWith(":")) {