Initial commit (working)
[fanfix.git] / src / be / nikiroo / fanfix / output / BasicOutput.java
1 package be.nikiroo.fanfix.output;
2
3 import java.io.File;
4 import java.io.IOException;
5
6 import be.nikiroo.fanfix.Instance;
7 import be.nikiroo.fanfix.bundles.StringId;
8 import be.nikiroo.fanfix.data.Chapter;
9 import be.nikiroo.fanfix.data.Paragraph;
10 import be.nikiroo.fanfix.data.Story;
11 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
12
13 /**
14 * This class is the base class used by the other output classes. It can be used
15 * outside of this package, and have static method that you can use to get
16 * access to the correct support class.
17 *
18 * @author niki
19 */
20 public abstract class BasicOutput {
21 /**
22 * The supported output types for which we can get a {@link BasicOutput}
23 * object.
24 *
25 * @author niki
26 */
27 public enum OutputType {
28 /** EPUB files created with this program */
29 EPUB,
30 /** Pure text file with some rules */
31 TEXT,
32 /** TEXT but with associated .info file */
33 INFO_TEXT,
34 /** DEBUG output to console */
35 SYSOUT,
36 /** ZIP with (PNG) images */
37 CBZ,
38 /** LaTeX file with "book" template */
39 LATEX;
40 public String toString() {
41 return super.toString().toLowerCase();
42 }
43
44 /**
45 * A description of this output type.
46 *
47 * @return the description
48 */
49 public String getDesc() {
50 String desc = Instance.getTrans().getStringX(StringId.OUTPUT_DESC,
51 this.name());
52
53 if (desc == null) {
54 desc = Instance.getTrans()
55 .getString(StringId.OUTPUT_DESC, this);
56 }
57
58 return desc;
59 }
60
61 /**
62 * Call {@link OutputType#valueOf(String.toUpperCase())}.
63 *
64 * @param typeName
65 * the possible type name
66 *
67 * @return NULL or the type
68 */
69 public static OutputType valueOfUC(String typeName) {
70 return OutputType.valueOf(typeName == null ? null : typeName
71 .toUpperCase());
72 }
73
74 /**
75 * Call {@link OutputType#valueOf(String.toUpperCase())} but return NULL
76 * for NULL instead of raising exception.
77 *
78 * @param typeName
79 * the possible type name
80 *
81 * @return NULL or the type
82 */
83 public static OutputType valueOfNullOkUC(String typeName) {
84 if (typeName == null) {
85 return null;
86 }
87
88 return OutputType.valueOfUC(typeName);
89 }
90
91 /**
92 * Call {@link OutputType#valueOf(String.toUpperCase())} but return NULL
93 * in case of error instead of raising an exception.
94 *
95 * @param typeName
96 * the possible type name
97 *
98 * @return NULL or the type
99 */
100 public static OutputType valueOfAllOkUC(String typeName) {
101 try {
102 return OutputType.valueOfUC(typeName);
103 } catch (Exception e) {
104 return null;
105 }
106 }
107 }
108
109 /** The creator name (this program, by me!) */
110 static final String EPUB_CREATOR = "Fanfix (by Niki)";
111
112 /** The current best name for an image */
113 private String imageName;
114 private File targetDir;
115 private String targetName;
116 private OutputType type;
117 private boolean writeCover;
118 private boolean writeInfo;
119
120 /**
121 * Process the {@link Story} into the given target.
122 *
123 * @param story
124 * the {@link Story} to export
125 * @param target
126 * the target where to save to (will not necessary be taken as is
127 * by the processor, for instance an extension can be added)
128 *
129 * @return the actual main target saved, which can be slightly different
130 * that the input one
131 *
132 * @throws IOException
133 * in case of I/O error
134 */
135 public File process(Story story, String target) throws IOException {
136 target = new File(target).getAbsolutePath();
137 File targetDir = new File(target).getParentFile();
138 String targetName = new File(target).getName();
139
140 String ext = getDefaultExtension();
141 if (ext != null && !ext.isEmpty()) {
142 if (targetName.toLowerCase().endsWith(ext)) {
143 targetName = targetName.substring(0,
144 targetName.length() - ext.length());
145 }
146 }
147
148 return process(story, targetDir, targetName);
149 }
150
151 /**
152 * Process the {@link Story} into the given target.
153 * <p>
154 * This method is expected to be overridden in most cases.
155 *
156 * @param story
157 * the {@link Story} to export
158 * @param targetDir
159 * the target dir where to save to
160 * @param targetName
161 * the target filename (will not necessary be taken as is by the
162 * processor, for instance an extension can be added)
163 *
164 * @return the actual main target saved, which can be slightly different
165 * that the input one
166 *
167 * @throws IOException
168 * in case of I/O error
169 */
170 protected File process(Story story, File targetDir, String targetName)
171 throws IOException {
172 this.targetDir = targetDir;
173 this.targetName = targetName;
174
175 writeStory(story);
176
177 return null;
178 }
179
180 /**
181 * The output type.
182 *
183 * @return the type
184 */
185 public OutputType getType() {
186 return type;
187 }
188
189 /**
190 * The output type.
191 *
192 * @param type
193 * the new type
194 * @param infoCover
195 * TRUE to enable the creation of a .info file and a cover if
196 * possible
197 *
198 * @return this
199 */
200 protected BasicOutput setType(OutputType type, boolean writeCover,
201 boolean writeInfo) {
202 this.type = type;
203 this.writeCover = writeCover;
204 this.writeInfo = writeInfo;
205
206 return this;
207 }
208
209 /**
210 * The default extension to add to the output files.
211 * <p>
212 * Cannot be NULL!
213 *
214 * @return the extension
215 */
216 protected String getDefaultExtension() {
217 return "";
218 }
219
220 protected void writeStoryHeader(Story story) throws IOException {
221 }
222
223 protected void writeChapterHeader(Chapter chap) throws IOException {
224 }
225
226 protected void writeParagraphHeader(Paragraph para) throws IOException {
227 }
228
229 protected void writeStoryFooter(Story story) throws IOException {
230 }
231
232 protected void writeChapterFooter(Chapter chap) throws IOException {
233 }
234
235 protected void writeParagraphFooter(Paragraph para) throws IOException {
236 }
237
238 protected void writeStory(Story story) throws IOException {
239 String chapterNameNum = String.format("%03d", 0);
240 String paragraphNumber = String.format("%04d", 0);
241 imageName = paragraphNumber + "_" + chapterNameNum + ".png";
242
243 if (writeCover) {
244 InfoCover.writeCover(targetDir, targetName, story.getMeta());
245 }
246 if (writeInfo) {
247 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
248 }
249
250 writeStoryHeader(story);
251 for (Chapter chap : story) {
252 writeChapter(chap);
253 }
254 writeStoryFooter(story);
255 }
256
257 protected void writeChapter(Chapter chap) throws IOException {
258 String chapterNameNum;
259 if (chap.getName() == null || chap.getName().isEmpty()) {
260 chapterNameNum = String.format("%03d", chap.getNumber());
261 } else {
262 chapterNameNum = String.format("%03d", chap.getNumber()) + "_"
263 + chap.getName().replace(" ", "_");
264 }
265
266 int num = 0;
267 String paragraphNumber = String.format("%04d", num++);
268 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
269
270 writeChapterHeader(chap);
271 for (Paragraph para : chap) {
272 paragraphNumber = String.format("%04d", num++);
273 imageName = chapterNameNum + "_" + paragraphNumber + ".png";
274 writeParagraph(para);
275 }
276 writeChapterFooter(chap);
277 }
278
279 protected void writeParagraph(Paragraph para) throws IOException {
280 writeParagraphHeader(para);
281 writeTextLine(para.getType(), para.getContent());
282 writeParagraphFooter(para);
283 }
284
285 protected void writeTextLine(ParagraphType type, String line)
286 throws IOException {
287 }
288
289 /**
290 * Return the current best guess for an image name, based upon the current
291 * {@link Chapter} and {@link Paragraph}.
292 *
293 * @param prefix
294 * add the original target name as a prefix
295 *
296 * @return the guessed name
297 */
298 protected String getCurrentImageBestName(boolean prefix) {
299 if (prefix) {
300 return targetName + "_" + imageName;
301 }
302
303 return imageName;
304 }
305
306 /**
307 * Return the given word or sentence as <b>bold</b>.
308 *
309 * @param word
310 * the input
311 *
312 * @return the bold output
313 */
314 protected String enbold(String word) {
315 return word;
316 }
317
318 /**
319 * Return the given word or sentence as <i>italic</i>.
320 *
321 * @param word
322 * the input
323 *
324 * @return the italic output
325 */
326 protected String italize(String word) {
327 return word;
328 }
329
330 /**
331 * Decorate the given text with <b>bold</b> and <i>italic</i> words,
332 * according to {@link BasicOutput#enbold(String)} and
333 * {@link BasicOutput#italize(String)}.
334 *
335 * @param text
336 * the input
337 *
338 * @return the decorated output
339 */
340 protected String decorateText(String text) {
341 StringBuilder builder = new StringBuilder();
342
343 int bold = -1;
344 int italic = -1;
345 char prev = '\0';
346 for (char car : text.toCharArray()) {
347 switch (car) {
348 case '*':
349 if (bold >= 0 && prev != ' ') {
350 String data = builder.substring(bold);
351 builder.setLength(bold);
352 builder.append(enbold(data));
353 bold = -1;
354 } else if (bold < 0
355 && (prev == ' ' || prev == '\0' || prev == '\n')) {
356 bold = builder.length();
357 } else {
358 builder.append(car);
359 }
360
361 break;
362 case '_':
363 if (italic >= 0 && prev != ' ') {
364 String data = builder.substring(italic);
365 builder.setLength(italic);
366 builder.append(enbold(data));
367 italic = -1;
368 } else if (italic < 0
369 && (prev == ' ' || prev == '\0' || prev == '\n')) {
370 italic = builder.length();
371 } else {
372 builder.append(car);
373 }
374
375 break;
376 default:
377 builder.append(car);
378 break;
379 }
380
381 prev = car;
382 }
383
384 if (bold >= 0) {
385 builder.insert(bold, '*');
386 }
387
388 if (italic >= 0) {
389 builder.insert(italic, '_');
390 }
391
392 return builder.toString();
393 }
394
395 /**
396 * Return a {@link BasicOutput} object compatible with the given
397 * {@link OutputType}.
398 *
399 * @param type
400 * the type
401 * @param infoCover
402 * force the <tt>.info</tt> file and the cover to be saved next
403 * to the main target file
404 *
405 * @return the {@link BasicOutput}
406 */
407 public static BasicOutput getOutput(OutputType type, boolean infoCover) {
408 if (type != null) {
409 switch (type) {
410 case EPUB:
411 return new Epub().setType(type, infoCover, infoCover);
412 case TEXT:
413 return new Text().setType(type, true, infoCover);
414 case INFO_TEXT:
415 return new InfoText().setType(type, true, true);
416 case SYSOUT:
417 return new Sysout().setType(type, false, false);
418 case CBZ:
419 return new Cbz().setType(type, infoCover, infoCover);
420 case LATEX:
421 return new LaTeX().setType(type, infoCover, infoCover);
422 }
423 }
424
425 return null;
426 }
427 }