1 package be
.nikiroo
.fanfix
.supported
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.net
.URISyntaxException
;
8 import java
.util
.AbstractMap
;
9 import java
.util
.ArrayList
;
10 import java
.util
.List
;
11 import java
.util
.Map
.Entry
;
12 import java
.util
.Scanner
;
14 import org
.jsoup
.nodes
.Document
;
16 import be
.nikiroo
.fanfix
.Instance
;
17 import be
.nikiroo
.fanfix
.bundles
.Config
;
18 import be
.nikiroo
.fanfix
.data
.Chapter
;
19 import be
.nikiroo
.fanfix
.data
.MetaData
;
20 import be
.nikiroo
.fanfix
.data
.Paragraph
;
21 import be
.nikiroo
.utils
.Image
;
22 import be
.nikiroo
.utils
.ImageUtils
;
23 import be
.nikiroo
.utils
.Progress
;
24 import be
.nikiroo
.utils
.streams
.MarkableFileInputStream
;
27 * Support class for local stories encoded in textual format, with a few rules:
29 * <li>The title must be on the first line</li>
30 * <li>The author (preceded by nothing, "by " or "©") must be on the second
31 * line, possibly with the publication date in parenthesis (i.e., "
32 * <tt>By Unknown (3rd October 1998)</tt>")</li>
33 * <li>Chapters must be declared with "<tt>Chapter x</tt>" or "
34 * <tt>Chapter x: NAME OF THE CHAPTER</tt>", where "<tt>x</tt>" is the chapter
36 * <li>A description of the story must be given as chapter number 0</li>
37 * <li>A cover may be present, with the same filename but a PNG, JPEG or JPG
43 class Text
extends BasicSupport
{
44 private File sourceFile
;
45 private InputStream in
;
47 protected File
getSourceFile() {
51 protected InputStream
getInput() {
55 } catch (IOException e
) {
56 Instance
.getInstance().getTraceHandler().error(new IOException("Cannot reset the Text stream", e
));
66 protected boolean isHtml() {
71 protected Document
loadDocument(URL source
) throws IOException
{
73 sourceFile
= new File(source
.toURI());
74 in
= new MarkableFileInputStream(sourceFile
);
75 } catch (URISyntaxException e
) {
76 throw new IOException("Cannot load the text document: " + source
);
83 protected MetaData
getMeta() throws IOException
{
84 MetaData meta
= new MetaData();
86 meta
.setTitle(getTitle());
87 meta
.setAuthor(getAuthor());
88 meta
.setDate(bsHelper
.formatDate(getDate()));
89 meta
.setTags(new ArrayList
<String
>());
90 meta
.setSource(getType().getSourceName());
91 meta
.setUrl(getSourceFile().toURI().toURL().toString());
92 meta
.setPublisher("");
93 meta
.setUuid(getSourceFile().toString());
95 meta
.setLang(getLang()); // default is EN
96 meta
.setSubject(getSourceFile().getParentFile().getName());
97 meta
.setType(getType().toString());
98 meta
.setImageDocument(false);
99 meta
.setCover(getCover(getSourceFile()));
104 private String
getLang() {
105 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
106 Scanner scan
= new Scanner(getInput(), "UTF-8");
107 scan
.useDelimiter("\\n");
108 scan
.next(); // Title
109 scan
.next(); // Author (Date)
110 String chapter0
= scan
.next(); // empty or Chapter 0
111 while (chapter0
.isEmpty()) {
112 chapter0
= scan
.next();
115 String lang
= detectChapter(chapter0
, 0);
118 lang
= detectChapter(chapter0
, 1);
124 lang
= lang
.toLowerCase();
130 private String
getTitle() {
131 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
132 Scanner scan
= new Scanner(getInput(), "UTF-8");
133 scan
.useDelimiter("\\n");
137 private String
getAuthor() {
138 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
139 Scanner scan
= new Scanner(getInput(), "UTF-8");
140 scan
.useDelimiter("\\n");
142 String authorDate
= scan
.next();
144 String author
= authorDate
;
145 int pos
= authorDate
.indexOf('(');
147 author
= authorDate
.substring(0, pos
);
150 return bsHelper
.fixAuthor(author
);
153 private String
getDate() {
154 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
155 Scanner scan
= new Scanner(getInput(), "UTF-8");
156 scan
.useDelimiter("\\n");
158 String authorDate
= scan
.next();
161 int pos
= authorDate
.indexOf('(');
163 date
= authorDate
.substring(pos
+ 1).trim();
164 pos
= date
.lastIndexOf(')');
166 date
= date
.substring(0, pos
).trim();
174 protected String
getDesc() throws IOException
{
175 String content
= getChapterContent(null, 0, null).trim();
176 if (!content
.isEmpty()) {
177 Chapter desc
= bsPara
.makeChapter(this, null, 0, "Description",
178 content
, isHtml(), null);
179 StringBuilder builder
= new StringBuilder();
180 for (Paragraph para
: desc
) {
181 if (builder
.length() > 0) {
182 builder
.append("\n");
184 builder
.append(para
.getContent());
191 protected Image
getCover(File sourceFile
) {
192 String path
= sourceFile
.getName();
194 for (String ext
: new String
[] { ".txt", ".text", ".story" }) {
195 if (path
.endsWith(ext
)) {
196 path
= path
.substring(0, path
.length() - ext
.length());
200 Image cover
= bsImages
.getImage(this, sourceFile
.getParentFile(), path
);
203 File tmp
= Instance
.getInstance().getTempFiles().createTempFile("test_cover_image");
204 ImageUtils
.getInstance().saveAsImage(cover
, tmp
, "png");
206 } catch (IOException e
) {
215 protected List
<Entry
<String
, URL
>> getChapters(Progress pg
)
217 List
<Entry
<String
, URL
>> chaps
= new ArrayList
<Entry
<String
, URL
>>();
218 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
219 Scanner scan
= new Scanner(getInput(), "UTF-8");
220 scan
.useDelimiter("\\n");
221 String line
= "first is not empty";
222 while (scan
.hasNext()) {
223 boolean prevLineEmpty
= line
.trim().isEmpty();
225 if (prevLineEmpty
&& detectChapter(line
, chaps
.size() + 1) != null) {
226 String chapName
= Integer
.toString(chaps
.size() + 1);
227 int pos
= line
.indexOf(':');
228 if (pos
>= 0 && pos
+ 1 < line
.length()) {
229 chapName
= line
.substring(pos
+ 1).trim();
232 chaps
.add(new AbstractMap
.SimpleEntry
<String
, URL
>(//
234 getSourceFile().toURI().toURL()));
242 protected String
getChapterContent(URL source
, int number
, Progress pg
)
244 StringBuilder builder
= new StringBuilder();
245 @SuppressWarnings("resource") // cannot close, or we loose getInput()!
246 Scanner scan
= new Scanner(getInput(), "UTF-8");
247 scan
.useDelimiter("\\n");
248 scan
.next(); // title
249 scan
.next(); // author
250 scan
.next(); // date or empty
251 Boolean inChap
= null;
253 while (scan
.hasNext()) {
254 if (number
== 0 && !line
.trim().isEmpty()) {
255 // We found pre-chapter content, we are checking for
256 // Chapter 0 (fake chapter) --> keep the content
261 if ((inChap
== null || !inChap
) && detectChapter(line
, number
) != null) {
263 } else if (detectChapter(line
, number
+ 1) != null) {
265 } else if (inChap
!= null && inChap
) {
266 builder
.append(line
);
267 builder
.append("\n");
271 return builder
.toString();
275 protected void close() {
276 InputStream in
= getInput();
280 } catch (IOException e
) {
281 Instance
.getInstance().getTraceHandler()
282 .error(new IOException("Cannot close the text source file input", e
));
290 protected boolean supports(URL url
) {
291 return supports(url
, false);
295 * Check if we supports this {@link URL}, that is, if the info file can be
296 * found OR not found.
298 * It must also be a file, not another kind of URL.
301 * the {@link URL} to check
303 * TRUE to require the info file, FALSE to forbid the info file
305 * @return TRUE if it is supported
307 protected boolean supports(URL url
, boolean info
) {
308 if (!"file".equals(url
.getProtocol())) {
312 boolean infoPresent
= false;
315 file
= new File(url
.toURI());
316 file
= assureNoTxt(file
);
317 file
= new File(file
.getPath() + ".info");
318 } catch (URISyntaxException e
) {
319 Instance
.getInstance().getTraceHandler().error(e
);
323 infoPresent
= (file
!= null && file
.exists());
325 return infoPresent
== info
;
329 * Remove the ".txt" (or ".text") extension if it is present.
332 * the file to process
334 * @return the same file or a copy of it without the ".txt" extension if it
337 protected File
assureNoTxt(File file
) {
338 for (String ext
: new String
[] { ".txt", ".text" }) {
339 if (file
.getName().endsWith(ext
)) {
340 file
= new File(file
.getPath().substring(0,
341 file
.getPath().length() - ext
.length()));
349 * Check if the given line looks like the given starting chapter in a
350 * supported language, and return the language if it does (or NULL if not).
355 * the specific chapter number to check for
357 * @return the language or NULL
359 static private String
detectChapter(String line
, int number
) {
360 line
= line
.toUpperCase();
361 for (String lang
: Instance
.getInstance().getConfig().getList(Config
.CONF_CHAPTER
)) {
362 String chapter
= Instance
.getInstance().getConfig().getStringX(Config
.CONF_CHAPTER
, lang
);
363 if (chapter
!= null && !chapter
.isEmpty()) {
364 chapter
= chapter
.toUpperCase() + " ";
365 if (line
.startsWith(chapter
)) {
366 // We want "[CHAPTER] [number]: [name]", with ": [name]"
368 String test
= line
.substring(chapter
.length()).trim();
370 String possibleNum
= test
.trim();
371 if (possibleNum
.indexOf(':') > 0) {
372 possibleNum
= possibleNum
.substring(0,
373 possibleNum
.indexOf(':')).trim();
376 if (test
.startsWith(Integer
.toString(number
))) {
378 .substring(Integer
.toString(number
).length())
380 if (test
.isEmpty() || test
.startsWith(":")) {