stupid crlf
[fanfix.git] / src / be / nikiroo / fanfix / supported / Text.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix.supported;
2
3import java.io.File;
4import java.io.IOException;
5import java.io.InputStream;
6import java.net.URISyntaxException;
7import java.net.URL;
7445f856 8import java.util.AbstractMap;
08fe2e33
NR
9import java.util.ArrayList;
10import java.util.List;
11import java.util.Map.Entry;
12import java.util.Scanner;
13
7445f856
NR
14import org.jsoup.nodes.Document;
15
08fe2e33
NR
16import be.nikiroo.fanfix.Instance;
17import be.nikiroo.fanfix.bundles.Config;
32097898 18import be.nikiroo.fanfix.data.Chapter;
68686a37 19import be.nikiroo.fanfix.data.MetaData;
32097898 20import be.nikiroo.fanfix.data.Paragraph;
16a81ef7 21import be.nikiroo.utils.Image;
81b5e730 22import be.nikiroo.utils.ImageUtils;
ed08c171 23import be.nikiroo.utils.Progress;
8d59ce07 24import be.nikiroo.utils.streams.MarkableFileInputStream;
08fe2e33
NR
25
26/**
27 * Support class for local stories encoded in textual format, with a few rules:
28 * <ul>
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
35 * number</li>
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
48f14dc9 38 * extension</li>
08fe2e33
NR
39 * </ul>
40 *
41 * @author niki
42 */
7445f856
NR
43class Text extends BasicSupport {
44 private File sourceFile;
45 private InputStream in;
46
47 protected File getSourceFile() {
48 return sourceFile;
49 }
50
51 protected InputStream getInput() {
52 if (in != null) {
53 try {
54 in.reset();
55 } catch (IOException e) {
56 Instance.getTraceHandler().error(
57 new IOException("Cannot reset the Text stream", e));
58 }
59
60 return in;
61 }
62
63 return null;
64 }
65
08fe2e33
NR
66 @Override
67 protected boolean isHtml() {
68 return false;
69 }
70
08fe2e33 71 @Override
7445f856
NR
72 protected Document loadDocument(URL source) throws IOException {
73 try {
74 sourceFile = new File(source.toURI());
67837328 75 in = new MarkableFileInputStream(sourceFile);
7445f856
NR
76 } catch (URISyntaxException e) {
77 throw new IOException("Cannot load the text document: " + source);
78 }
79
80 return null;
81 }
82
83 @Override
84 protected MetaData getMeta() throws IOException {
68686a37
NR
85 MetaData meta = new MetaData();
86
7445f856
NR
87 meta.setTitle(getTitle());
88 meta.setAuthor(getAuthor());
89 meta.setDate(getDate());
68686a37 90 meta.setTags(new ArrayList<String>());
727108fe 91 meta.setSource(getType().getSourceName());
7445f856 92 meta.setUrl(getSourceFile().toURI().toURL().toString());
2206ef66 93 meta.setPublisher("");
7445f856 94 meta.setUuid(getSourceFile().toString());
68686a37 95 meta.setLuid("");
7445f856
NR
96 meta.setLang(getLang()); // default is EN
97 meta.setSubject(getSourceFile().getParentFile().getName());
68686a37
NR
98 meta.setType(getType().toString());
99 meta.setImageDocument(false);
7445f856 100 meta.setCover(getCover(getSourceFile()));
68686a37
NR
101
102 return meta;
08fe2e33
NR
103 }
104
7445f856 105 private String getLang() {
08fe2e33 106 @SuppressWarnings("resource")
7445f856 107 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33
NR
108 scan.useDelimiter("\\n");
109 scan.next(); // Title
110 scan.next(); // Author (Date)
111 String chapter0 = scan.next(); // empty or Chapter 0
112 while (chapter0.isEmpty()) {
113 chapter0 = scan.next();
114 }
115
22848428
NR
116 String lang = detectChapter(chapter0, 0);
117 if (lang == null) {
118 // No description??
119 lang = detectChapter(chapter0, 1);
120 }
121
08fe2e33 122 if (lang == null) {
276f95c6 123 lang = "en";
08fe2e33 124 } else {
276f95c6 125 lang = lang.toLowerCase();
08fe2e33
NR
126 }
127
128 return lang;
129 }
130
7445f856 131 private String getTitle() {
08fe2e33 132 @SuppressWarnings("resource")
7445f856 133 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33
NR
134 scan.useDelimiter("\\n");
135 return scan.next();
136 }
137
7445f856 138 private String getAuthor() {
08fe2e33 139 @SuppressWarnings("resource")
7445f856 140 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33
NR
141 scan.useDelimiter("\\n");
142 scan.next();
143 String authorDate = scan.next();
144
145 String author = authorDate;
146 int pos = authorDate.indexOf('(');
147 if (pos >= 0) {
148 author = authorDate.substring(0, pos);
149 }
150
8d59ce07 151 return bsHelper.fixAuthor(author);
08fe2e33
NR
152 }
153
7445f856 154 private String getDate() {
08fe2e33 155 @SuppressWarnings("resource")
7445f856 156 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33
NR
157 scan.useDelimiter("\\n");
158 scan.next();
159 String authorDate = scan.next();
160
161 String date = "";
162 int pos = authorDate.indexOf('(');
163 if (pos >= 0) {
164 date = authorDate.substring(pos + 1).trim();
165 pos = date.lastIndexOf(')');
166 if (pos >= 0) {
167 date = date.substring(0, pos).trim();
168 }
169 }
170
171 return date;
172 }
173
174 @Override
7445f856 175 protected String getDesc() throws IOException {
32097898
NR
176 String content = getChapterContent(null, 0, null).trim();
177 if (!content.isEmpty()) {
178 Chapter desc = bsPara.makeChapter(this, null, 0, "Description",
179 content, isHtml(), null);
180 StringBuilder builder = new StringBuilder();
181 for (Paragraph para : desc) {
182 if (builder.length() > 0) {
183 builder.append("\n");
184 }
185 builder.append(para.getContent());
186 }
187 }
188
189 return content;
08fe2e33
NR
190 }
191
7445f856
NR
192 private Image getCover(File sourceFile) {
193 String path = sourceFile.getName();
08fe2e33
NR
194
195 for (String ext : new String[] { ".txt", ".text", ".story" }) {
196 if (path.endsWith(ext)) {
197 path = path.substring(0, path.length() - ext.length());
198 }
199 }
200
32097898 201 Image cover = bsImages.getImage(this, sourceFile.getParentFile(), path);
81b5e730
NR
202 if (cover != null) {
203 try {
204 File tmp = Instance.getTempFiles().createTempFile(
205 "test_cover_image");
206 ImageUtils.getInstance().saveAsImage(cover, tmp, "png");
207 tmp.delete();
208 } catch (IOException e) {
209 cover = null;
210 }
211 }
212
213 return cover;
08fe2e33
NR
214 }
215
216 @Override
7445f856
NR
217 protected List<Entry<String, URL>> getChapters(Progress pg)
218 throws IOException {
08fe2e33
NR
219 List<Entry<String, URL>> chaps = new ArrayList<Entry<String, URL>>();
220 @SuppressWarnings("resource")
7445f856 221 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33 222 scan.useDelimiter("\\n");
08fe2e33
NR
223 boolean prevLineEmpty = false;
224 while (scan.hasNext()) {
225 String line = scan.next();
22848428
NR
226 if (prevLineEmpty && detectChapter(line, chaps.size() + 1) != null) {
227 String chapName = Integer.toString(chaps.size() + 1);
228 int pos = line.indexOf(':');
229 if (pos >= 0 && pos + 1 < line.length()) {
230 chapName = line.substring(pos + 1).trim();
231 }
08fe2e33 232
7445f856
NR
233 chaps.add(new AbstractMap.SimpleEntry<String, URL>(//
234 chapName, //
235 getSourceFile().toURI().toURL()));
08fe2e33
NR
236 }
237
238 prevLineEmpty = line.trim().isEmpty();
239 }
240
241 return chaps;
242 }
243
244 @Override
7445f856
NR
245 protected String getChapterContent(URL source, int number, Progress pg)
246 throws IOException {
08fe2e33
NR
247 StringBuilder builder = new StringBuilder();
248 @SuppressWarnings("resource")
7445f856 249 Scanner scan = new Scanner(getInput(), "UTF-8");
08fe2e33
NR
250 scan.useDelimiter("\\n");
251 boolean inChap = false;
08fe2e33
NR
252 while (scan.hasNext()) {
253 String line = scan.next();
32097898 254 if (!inChap && detectChapter(line, number) != null) {
68686a37 255 inChap = true;
32097898 256 } else if (detectChapter(line, number + 1) != null) {
68686a37
NR
257 break;
258 } else if (inChap) {
259 builder.append(line);
260 builder.append("\n");
08fe2e33 261 }
08fe2e33
NR
262 }
263
264 return builder.toString();
265 }
266
7445f856
NR
267 @Override
268 protected void close() {
269 InputStream in = getInput();
270 if (in != null) {
271 try {
272 in.close();
273 } catch (IOException e) {
274 Instance.getTraceHandler().error(
275 new IOException(
276 "Cannot close the text source file input", e));
277 }
278 }
279
280 super.close();
281 }
282
08fe2e33
NR
283 @Override
284 protected boolean supports(URL url) {
86d49dbc
NR
285 return supports(url, false);
286 }
287
288 /**
289 * Check if we supports this {@link URL}, that is, if the info file can be
290 * found OR not found.
291 *
292 * @param url
293 * the {@link URL} to check
294 * @param info
295 * TRUE to require the info file, FALSE to forbid the info file
296 *
297 * @return TRUE if it is supported
298 */
299 protected boolean supports(URL url, boolean info) {
300 boolean infoPresent = false;
08fe2e33
NR
301 if ("file".equals(url.getProtocol())) {
302 File file;
303 try {
304 file = new File(url.toURI());
86d49dbc 305 file = assureNoTxt(file);
08fe2e33
NR
306 file = new File(file.getPath() + ".info");
307 } catch (URISyntaxException e) {
62c63b07 308 Instance.getTraceHandler().error(e);
08fe2e33
NR
309 file = null;
310 }
311
86d49dbc 312 infoPresent = (file != null && file.exists());
08fe2e33
NR
313 }
314
86d49dbc
NR
315 return infoPresent == info;
316 }
317
318 /**
319 * Remove the ".txt" extension if it is present.
320 *
321 * @param file
322 * the file to process
323 *
324 * @return the same file or a copy of it without the ".txt" extension if it
325 * was present
326 */
327 protected File assureNoTxt(File file) {
328 if (file.getName().endsWith(".txt")) {
329 file = new File(file.getPath().substring(0,
330 file.getPath().length() - 4));
331 }
332
333 return file;
08fe2e33
NR
334 }
335
08fe2e33
NR
336 /**
337 * Check if the given line looks like the given starting chapter in a
338 * supported language, and return the language if it does (or NULL if not).
339 *
340 * @param line
341 * the line to check
32097898
NR
342 * @param number
343 * the specific chapter number to check for
08fe2e33
NR
344 *
345 * @return the language or NULL
346 */
7445f856 347 static private String detectChapter(String line, int number) {
08fe2e33 348 line = line.toUpperCase();
13fdb89a 349 for (String lang : Instance.getConfig().getList(Config.CONF_CHAPTER)) {
32097898
NR
350 String chapter = Instance.getConfig().getStringX(
351 Config.CONF_CHAPTER, lang);
08fe2e33
NR
352 if (chapter != null && !chapter.isEmpty()) {
353 chapter = chapter.toUpperCase() + " ";
354 if (line.startsWith(chapter)) {
22848428
NR
355 // We want "[CHAPTER] [number]: [name]", with ": [name]"
356 // optional
357 String test = line.substring(chapter.length()).trim();
32097898
NR
358
359 String possibleNum = test.trim();
360 if (possibleNum.indexOf(':') > 0) {
361 possibleNum = possibleNum.substring(0,
362 possibleNum.indexOf(':')).trim();
363 }
364
22848428
NR
365 if (test.startsWith(Integer.toString(number))) {
366 test = test
367 .substring(Integer.toString(number).length())
368 .trim();
369 if (test.isEmpty() || test.startsWith(":")) {
370 return lang;
08fe2e33 371 }
08fe2e33
NR
372 }
373 }
374 }
375 }
376
377 return null;
378 }
379}