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