Merge branch 'master' into subtree
[nikiroo-utils.git] / output / BasicOutput.java
1 package be.nikiroo.fanfix.output;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import be.nikiroo.fanfix.Instance;
9 import be.nikiroo.fanfix.bundles.StringId;
10 import be.nikiroo.fanfix.data.Chapter;
11 import be.nikiroo.fanfix.data.Paragraph;
12 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
13 import be.nikiroo.fanfix.data.Story;
14 import be.nikiroo.utils.Progress;
15 import be.nikiroo.utils.Version;
16
17 /**
18 * This class is the base class used by the other output classes. It can be used
19 * outside of this package, and have static method that you can use to get
20 * access to the correct support class.
21 *
22 * @author niki
23 */
24 public abstract class BasicOutput {
25 /**
26 * The supported output types for which we can get a {@link BasicOutput}
27 * object.
28 *
29 * @author niki
30 */
31 public enum OutputType {
32 /** EPUB files created with this program */
33 EPUB,
34 /** Pure text file with some rules */
35 TEXT,
36 /** TEXT but with associated .info file */
37 INFO_TEXT,
38 /** DEBUG output to console */
39 SYSOUT,
40 /** ZIP with (PNG) images */
41 CBZ,
42 /** LaTeX file with "book" template */
43 LATEX,
44 /** HTML files in a dedicated directory */
45 HTML,
46
47 ;
48
49 @Override
50 public String toString() {
51 return super.toString().toLowerCase();
52 }
53
54 /**
55 * A description of this output type.
56 *
57 * @param longDesc
58 * TRUE for the long description, FALSE for the short one
59 *
60 * @return the description
61 */
62 public String getDesc(boolean longDesc) {
63 StringId id = longDesc ? StringId.OUTPUT_DESC
64 : StringId.OUTPUT_DESC_SHORT;
65
66 String desc = Instance.getInstance().getTrans().getStringX(id, this.name());
67
68 if (desc == null) {
69 desc = Instance.getInstance().getTrans().getString(id, this.toString());
70 }
71
72 if (desc == null || desc.isEmpty()) {
73 desc = this.toString();
74 }
75
76 return desc;
77 }
78
79 /**
80 * The default extension to add to the output files.
81 *
82 * @param readerTarget
83 * TRUE to point to the main {@link Story} entry point for a
84 * reader (for instance, the main entry point if this
85 * {@link Story} is in a directory bundle), FALSE to point to
86 * the main file even if it is a directory for instance
87 *
88 * @return the extension
89 */
90 public String getDefaultExtension(boolean readerTarget) {
91 BasicOutput output = BasicOutput.getOutput(this, false, false);
92 if (output != null) {
93 return output.getDefaultExtension(readerTarget);
94 }
95
96 return null;
97 }
98
99 /**
100 * Call {@link OutputType#valueOf(String)} after conversion to upper
101 * case.
102 *
103 * @param typeName
104 * the possible type name
105 *
106 * @return NULL or the type
107 */
108 public static OutputType valueOfUC(String typeName) {
109 return OutputType.valueOf(typeName == null ? null : typeName
110 .toUpperCase());
111 }
112
113 /**
114 * Call {@link OutputType#valueOf(String)} after conversion to upper
115 * case but return def for NULL and empty instead of raising an
116 * exception.
117 *
118 * @param typeName
119 * the possible type name
120 * @param def
121 * the default value
122 *
123 * @return NULL or the type
124 */
125 public static OutputType valueOfNullOkUC(String typeName, OutputType def) {
126 if (typeName == null || typeName.isEmpty()) {
127 return def;
128 }
129
130 return OutputType.valueOfUC(typeName);
131 }
132
133 /**
134 * Call {@link OutputType#valueOf(String)} after conversion to upper
135 * case but return def in case of error instead of raising an exception.
136 *
137 * @param typeName
138 * the possible type name
139 * @param def
140 * the default value
141 *
142 * @return NULL or the type
143 */
144 public static OutputType valueOfAllOkUC(String typeName, OutputType def) {
145 try {
146 return OutputType.valueOfUC(typeName);
147 } catch (Exception e) {
148 return def;
149 }
150 }
151 }
152
153 /** The creator name (this program, by me!) */
154 static protected final String EPUB_CREATOR = "Fanfix "
155 + Version.getCurrentVersion() + " (by Niki)";
156
157 /** The current best name for an image */
158 private String imageName;
159 private File targetDir;
160 private String targetName;
161 private OutputType type;
162 private boolean writeCover;
163 private boolean writeInfo;
164 private Progress storyPg;
165 private Progress chapPg;
166
167 /**
168 * Process the {@link Story} into the given target.
169 *
170 * @param story
171 * the {@link Story} to export
172 * @param target
173 * the target where to save to (will not necessary be taken as is
174 * by the processor, for instance an extension can be added)
175 * @param pg
176 * the optional progress reporter
177 *
178 * @return the actual main target saved, which can be slightly different
179 * that the input one
180 *
181 * @throws IOException
182 * in case of I/O error
183 */
184 public File process(Story story, String target, Progress pg)
185 throws IOException {
186 storyPg = pg;
187
188 File targetDir = null;
189 String targetName = null;
190 if (target != null) {
191 target = new File(target).getAbsolutePath();
192 targetDir = new File(target).getParentFile();
193 targetName = new File(target).getName();
194
195 String ext = getDefaultExtension(false);
196 if (ext != null && !ext.isEmpty()) {
197 if (targetName.toLowerCase().endsWith(ext)) {
198 targetName = targetName.substring(0, targetName.length()
199 - ext.length());
200 }
201 }
202 }
203
204 return process(story, targetDir, targetName);
205 }
206
207 /**
208 * Process the {@link Story} into the given target.
209 * <p>
210 * This method is expected to be overridden in most cases.
211 *
212 * @param story
213 * the {@link Story} to export
214 * @param targetDir
215 * the target dir where to save to
216 * @param targetName
217 * the target filename (will not necessary be taken as is by the
218 * processor, for instance an extension can be added)
219 *
220 *
221 * @return the actual main target saved, which can be slightly different
222 * that the input one
223 *
224 * @throws IOException
225 * in case of I/O error
226 */
227 protected File process(Story story, File targetDir, String targetName)
228 throws IOException {
229 this.targetDir = targetDir;
230 this.targetName = targetName;
231
232 writeStory(story);
233
234 return null;
235 }
236
237 /**
238 * The output type.
239 *
240 * @return the type
241 */
242 public OutputType getType() {
243 return type;
244 }
245
246 /**
247 * Enable the creation of a .info file next to the resulting processed file.
248 *
249 * @return TRUE to enable it
250 */
251 protected boolean isWriteInfo() {
252 return writeInfo;
253 }
254
255 /**
256 * Enable the creation of a cover file next to the resulting processed file
257 * if possible.
258 *
259 * @return TRUE to enable it
260 */
261 protected boolean isWriteCover() {
262 return writeCover;
263 }
264
265 /**
266 * The output type.
267 *
268 * @param type
269 * the new type
270 * @param writeCover
271 * TRUE to enable the creation of a cover if possible
272 * @param writeInfo
273 * TRUE to enable the creation of a .info file
274 *
275 * @return this
276 */
277 protected BasicOutput setType(OutputType type, boolean writeInfo,
278 boolean writeCover) {
279 this.type = type;
280 this.writeInfo = writeInfo;
281 this.writeCover = writeCover;
282
283 return this;
284 }
285
286 /**
287 * The default extension to add to the output files.
288 *
289 * @param readerTarget
290 * TRUE to point to the main {@link Story} entry point for a
291 * reader (for instance, the main entry point if this
292 * {@link Story} is in a directory bundle), FALSE to point to the
293 * main file even if it is a directory for instance
294 *
295 * @return the extension
296 */
297 public String getDefaultExtension(
298 @SuppressWarnings("unused") boolean readerTarget) {
299 return "";
300 }
301
302 protected void writeStoryHeader(Story story) throws IOException {
303 }
304
305 protected void writeChapterHeader(Chapter chap) throws IOException {
306 }
307
308 protected void writeParagraphHeader(Paragraph para) throws IOException {
309 }
310
311 protected void writeStoryFooter(Story story) throws IOException {
312 }
313
314 protected void writeChapterFooter(Chapter chap) throws IOException {
315 }
316
317 protected void writeParagraphFooter(Paragraph para) throws IOException {
318 }
319
320 protected void writeStory(Story story) throws IOException {
321 if (storyPg == null) {
322 storyPg = new Progress(0, story.getChapters().size() + 2);
323 } else {
324 storyPg.setMinMax(0, story.getChapters().size() + 2);
325 }
326
327 String chapterNameNum = String.format("%03d", 0);
328 String paragraphNumber = String.format("%04d", 0);
329 imageName = paragraphNumber + "_" + chapterNameNum;
330
331 if (story.getMeta() != null) {
332 story.getMeta().setType("" + getType());
333 }
334
335 if (isWriteCover()) {
336 InfoCover.writeCover(targetDir, targetName, story.getMeta());
337 }
338 if (isWriteInfo()) {
339 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
340 }
341
342 storyPg.setProgress(1);
343
344 List<Progress> chapPgs = new ArrayList<Progress>(story.getChapters()
345 .size());
346 for (Chapter chap : story) {
347 chapPg = new Progress(0, chap.getParagraphs().size());
348 storyPg.addProgress(chapPg, 1);
349 chapPgs.add(chapPg);
350 chapPg = null;
351 }
352
353 writeStoryHeader(story);
354 for (int i = 0; i < story.getChapters().size(); i++) {
355 chapPg = chapPgs.get(i);
356 writeChapter(story.getChapters().get(i));
357 chapPg.setProgress(chapPg.getMax());
358 chapPg = null;
359 }
360 writeStoryFooter(story);
361
362 storyPg.setProgress(storyPg.getMax());
363 storyPg = null;
364 }
365
366 protected void writeChapter(Chapter chap) throws IOException {
367 String chapterNameNum;
368 if (chap.getName() == null || chap.getName().isEmpty()) {
369 chapterNameNum = String.format("%03d", chap.getNumber());
370 } else {
371 chapterNameNum = String.format("%03d", chap.getNumber()) + "_"
372 + chap.getName().replace(" ", "_");
373 }
374
375 int num = 0;
376 String paragraphNumber = String.format("%04d", num++);
377 imageName = chapterNameNum + "_" + paragraphNumber;
378
379 writeChapterHeader(chap);
380 int i = 1;
381 for (Paragraph para : chap) {
382 paragraphNumber = String.format("%04d", num++);
383 imageName = chapterNameNum + "_" + paragraphNumber;
384 writeParagraph(para);
385 if (chapPg != null) {
386 chapPg.setProgress(i++);
387 }
388 }
389 writeChapterFooter(chap);
390 }
391
392 protected void writeParagraph(Paragraph para) throws IOException {
393 writeParagraphHeader(para);
394 writeTextLine(para.getType(), para.getContent());
395 writeParagraphFooter(para);
396 }
397
398 @SuppressWarnings("unused")
399 protected void writeTextLine(ParagraphType type, String line)
400 throws IOException {
401 }
402
403 /**
404 * Return the current best guess for an image name, based upon the current
405 * {@link Chapter} and {@link Paragraph}.
406 *
407 * @param prefix
408 * add the original target name as a prefix
409 *
410 * @return the guessed name
411 */
412 protected String getCurrentImageBestName(boolean prefix) {
413 if (prefix) {
414 return targetName + "_" + imageName;
415 }
416
417 return imageName;
418 }
419
420 /**
421 * Return the given word or sentence as <b>bold</b>.
422 *
423 * @param word
424 * the input
425 *
426 * @return the bold output
427 */
428 protected String enbold(String word) {
429 return word;
430 }
431
432 /**
433 * Return the given word or sentence as <i>italic</i>.
434 *
435 * @param word
436 * the input
437 *
438 * @return the italic output
439 */
440 protected String italize(String word) {
441 return word;
442 }
443
444 /**
445 * Decorate the given text with <b>bold</b> and <i>italic</i> words,
446 * according to {@link BasicOutput#enbold(String)} and
447 * {@link BasicOutput#italize(String)}.
448 *
449 * @param text
450 * the input
451 *
452 * @return the decorated output
453 */
454 protected String decorateText(String text) {
455 StringBuilder builder = new StringBuilder();
456
457 int bold = -1;
458 int italic = -1;
459 char prev = '\0';
460 for (char car : text.toCharArray()) {
461 switch (car) {
462 case '*':
463 if (bold >= 0 && prev != ' ') {
464 String data = builder.substring(bold);
465 builder.setLength(bold);
466 builder.append(enbold(data));
467 bold = -1;
468 } else if (bold < 0
469 && (prev == ' ' || prev == '\0' || prev == '\n')) {
470 bold = builder.length();
471 } else {
472 builder.append(car);
473 }
474
475 break;
476 case '_':
477 if (italic >= 0 && prev != ' ') {
478 String data = builder.substring(italic);
479 builder.setLength(italic);
480 builder.append(enbold(data));
481 italic = -1;
482 } else if (italic < 0
483 && (prev == ' ' || prev == '\0' || prev == '\n')) {
484 italic = builder.length();
485 } else {
486 builder.append(car);
487 }
488
489 break;
490 default:
491 builder.append(car);
492 break;
493 }
494
495 prev = car;
496 }
497
498 if (bold >= 0) {
499 builder.insert(bold, '*');
500 }
501
502 if (italic >= 0) {
503 builder.insert(italic, '_');
504 }
505
506 return builder.toString();
507 }
508
509 /**
510 * Return a {@link BasicOutput} object compatible with the given
511 * {@link OutputType}.
512 *
513 * @param type
514 * the type
515 * @param writeCover
516 * TRUE to enable the creation of a cover if possible to be saved
517 * next to the main target file
518 * @param writeInfo
519 * TRUE to enable the creation of a .info file to be saved next
520 * to the main target file
521 *
522 * @return the {@link BasicOutput}
523 */
524 public static BasicOutput getOutput(OutputType type, boolean writeInfo,
525 boolean writeCover) {
526 if (type != null) {
527 switch (type) {
528 case EPUB:
529 return new Epub().setType(type, writeInfo, writeCover);
530 case TEXT:
531 return new Text().setType(type, writeInfo, true);
532 case INFO_TEXT:
533 return new InfoText().setType(type, true, true);
534 case SYSOUT:
535 return new Sysout().setType(type, false, false);
536 case CBZ:
537 return new Cbz().setType(type, writeInfo, writeCover);
538 case LATEX:
539 return new LaTeX().setType(type, writeInfo, writeCover);
540 case HTML:
541 return new Html().setType(type, writeInfo, writeCover);
542 }
543 }
544
545 return null;
546 }
547 }