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