Update nikiroo-utils, update Library
[fanfix.git] / src / be / nikiroo / fanfix / supported / Text.java
1 package be.nikiroo.fanfix.supported;
2
3 import java.awt.image.BufferedImage;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.net.URISyntaxException;
8 import java.net.URL;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.Map.Entry;
12 import java.util.Scanner;
13
14 import be.nikiroo.fanfix.Instance;
15 import be.nikiroo.fanfix.bundles.Config;
16 import be.nikiroo.fanfix.data.MetaData;
17 import be.nikiroo.utils.Progress;
18
19 /**
20 * Support class for local stories encoded in textual format, with a few rules:
21 * <ul>
22 * <li>The title must be on the first line</li>
23 * <li>The author (preceded by nothing, "by " or "©") must be on the second
24 * line, possibly with the publication date in parenthesis (i.e., "
25 * <tt>By Unknown (3rd October 1998)</tt>")</li>
26 * <li>Chapters must be declared with "<tt>Chapter x</tt>" or "
27 * <tt>Chapter x: NAME OF THE CHAPTER</tt>", where "<tt>x</tt>" is the chapter
28 * number</li>
29 * <li>A description of the story must be given as chapter number 0</li>
30 * <li>A cover may be present, with the same filename but a PNG, JPEG or JPG
31 * extension</li<
32 * </ul>
33 *
34 * @author niki
35 */
36 class Text extends BasicSupport {
37 @Override
38 protected boolean isHtml() {
39 return false;
40 }
41
42 @Override
43 public String getSourceName() {
44 return "text";
45 }
46
47 @Override
48 protected MetaData getMeta(URL source, InputStream in) throws IOException {
49 MetaData meta = new MetaData();
50
51 meta.setTitle(getTitle(reset(in)));
52 meta.setAuthor(getAuthor(reset(in)));
53 meta.setDate(getDate(reset(in)));
54 meta.setTags(new ArrayList<String>());
55 meta.setSource(getSourceName());
56 meta.setUrl(source.toString());
57 meta.setPublisher("");
58 meta.setUuid(source.toString());
59 meta.setLuid("");
60 meta.setLang(getLang(source, reset(in))); // default is EN
61 meta.setSubject(getSubject(source));
62 meta.setType(getType().toString());
63 meta.setImageDocument(false);
64 meta.setCover(getCover(source));
65
66 return meta;
67 }
68
69 private String getSubject(URL source) throws IOException {
70 try {
71 File file = new File(source.toURI());
72 return file.getParentFile().getName();
73 } catch (URISyntaxException e) {
74 throw new IOException("Cannot parse the URL to File: "
75 + source.toString(), e);
76 }
77
78 }
79
80 private String getLang(URL source, InputStream in) throws IOException {
81 @SuppressWarnings("resource")
82 Scanner scan = new Scanner(in, "UTF-8");
83 scan.useDelimiter("\\n");
84 scan.next(); // Title
85 scan.next(); // Author (Date)
86 String chapter0 = scan.next(); // empty or Chapter 0
87 while (chapter0.isEmpty()) {
88 chapter0 = scan.next();
89 }
90
91 String lang = detectChapter(chapter0, 0);
92 if (lang == null) {
93 // No description??
94 lang = detectChapter(chapter0, 1);
95 }
96
97 if (lang == null) {
98 lang = "EN";
99 } else {
100 lang = lang.toUpperCase();
101 }
102
103 return lang;
104 }
105
106 private String getTitle(InputStream in) throws IOException {
107 @SuppressWarnings("resource")
108 Scanner scan = new Scanner(in, "UTF-8");
109 scan.useDelimiter("\\n");
110 return scan.next();
111 }
112
113 private String getAuthor(InputStream in) throws IOException {
114 @SuppressWarnings("resource")
115 Scanner scan = new Scanner(in, "UTF-8");
116 scan.useDelimiter("\\n");
117 scan.next();
118 String authorDate = scan.next();
119
120 String author = authorDate;
121 int pos = authorDate.indexOf('(');
122 if (pos >= 0) {
123 author = authorDate.substring(0, pos);
124 }
125
126 return fixAuthor(author);
127 }
128
129 private String getDate(InputStream in) throws IOException {
130 @SuppressWarnings("resource")
131 Scanner scan = new Scanner(in, "UTF-8");
132 scan.useDelimiter("\\n");
133 scan.next();
134 String authorDate = scan.next();
135
136 String date = "";
137 int pos = authorDate.indexOf('(');
138 if (pos >= 0) {
139 date = authorDate.substring(pos + 1).trim();
140 pos = date.lastIndexOf(')');
141 if (pos >= 0) {
142 date = date.substring(0, pos).trim();
143 }
144 }
145
146 return date;
147 }
148
149 @Override
150 protected String getDesc(URL source, InputStream in) throws IOException {
151 return getChapterContent(source, in, 0, null);
152 }
153
154 private BufferedImage getCover(URL source) throws IOException {
155 String path;
156 try {
157 path = new File(source.toURI()).getPath();
158 } catch (URISyntaxException e) {
159 Instance.syserr(e);
160 path = null;
161 }
162
163 for (String ext : new String[] { ".txt", ".text", ".story" }) {
164 if (path.endsWith(ext)) {
165 path = path.substring(0, path.length() - ext.length());
166 }
167 }
168
169 return getImage(this, source, path);
170 }
171
172 @Override
173 protected List<Entry<String, URL>> getChapters(URL source, InputStream in,
174 Progress pg) throws IOException {
175 List<Entry<String, URL>> chaps = new ArrayList<Entry<String, URL>>();
176 @SuppressWarnings("resource")
177 Scanner scan = new Scanner(in, "UTF-8");
178 scan.useDelimiter("\\n");
179 boolean prevLineEmpty = false;
180 while (scan.hasNext()) {
181 String line = scan.next();
182 if (prevLineEmpty && detectChapter(line, chaps.size() + 1) != null) {
183 String chapName = Integer.toString(chaps.size() + 1);
184 int pos = line.indexOf(':');
185 if (pos >= 0 && pos + 1 < line.length()) {
186 chapName = line.substring(pos + 1).trim();
187 }
188 final URL value = source;
189 final String key = chapName;
190 chaps.add(new Entry<String, URL>() {
191 public URL setValue(URL value) {
192 return null;
193 }
194
195 public URL getValue() {
196 return value;
197 }
198
199 public String getKey() {
200 return key;
201 }
202 });
203 }
204
205 prevLineEmpty = line.trim().isEmpty();
206 }
207
208 return chaps;
209 }
210
211 @Override
212 protected String getChapterContent(URL source, InputStream in, int number,
213 Progress pg) throws IOException {
214 StringBuilder builder = new StringBuilder();
215 @SuppressWarnings("resource")
216 Scanner scan = new Scanner(in, "UTF-8");
217 scan.useDelimiter("\\n");
218 boolean inChap = false;
219 while (scan.hasNext()) {
220 String line = scan.next();
221 if (detectChapter(line, number) != null) {
222 inChap = true;
223 } else if (inChap && detectChapter(line, number + 1) != null) {
224 break;
225 } else if (inChap) {
226 builder.append(line);
227 builder.append("\n");
228 }
229 }
230
231 return builder.toString();
232 }
233
234 @Override
235 protected boolean supports(URL url) {
236 if ("file".equals(url.getProtocol())) {
237 File file;
238 try {
239 file = new File(url.toURI());
240 file = new File(file.getPath() + ".info");
241 } catch (URISyntaxException e) {
242 Instance.syserr(e);
243 file = null;
244 }
245
246 return file == null || !file.exists();
247 }
248
249 return false;
250 }
251
252 /**
253 * Check if the given line looks like the given starting chapter in a
254 * supported language, and return the language if it does (or NULL if not).
255 *
256 * @param line
257 * the line to check
258 *
259 * @return the language or NULL
260 */
261 private String detectChapter(String line, int number) {
262 line = line.toUpperCase();
263 for (String lang : Instance.getConfig().getString(Config.CHAPTER)
264 .split(",")) {
265 String chapter = Instance.getConfig().getStringX(Config.CHAPTER,
266 lang);
267 if (chapter != null && !chapter.isEmpty()) {
268 chapter = chapter.toUpperCase() + " ";
269 if (line.startsWith(chapter)) {
270 // We want "[CHAPTER] [number]: [name]", with ": [name]"
271 // optional
272 String test = line.substring(chapter.length()).trim();
273 if (test.startsWith(Integer.toString(number))) {
274 test = test
275 .substring(Integer.toString(number).length())
276 .trim();
277 if (test.isEmpty() || test.startsWith(":")) {
278 return lang;
279 }
280 }
281 }
282 }
283 }
284
285 return null;
286 }
287 }