use a .summary file
[fanfix.git] / src / be / nikiroo / fanfix / output / BasicOutput.java
CommitLineData
08fe2e33
NR
1package be.nikiroo.fanfix.output;
2
3import java.io.File;
4import java.io.IOException;
bee7dffe
NR
5import java.util.ArrayList;
6import java.util.List;
08fe2e33
NR
7
8import be.nikiroo.fanfix.Instance;
9import be.nikiroo.fanfix.bundles.StringId;
10import be.nikiroo.fanfix.data.Chapter;
11import be.nikiroo.fanfix.data.Paragraph;
08fe2e33 12import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
bee7dffe 13import be.nikiroo.fanfix.data.Story;
3b2b638f 14import be.nikiroo.utils.Progress;
6fd6ebe6 15import be.nikiroo.utils.Version;
08fe2e33
NR
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 */
24public 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 */
2206ef66
NR
43 LATEX,
44 /** HTML files in a dedicated directory */
45 HTML,
46
47 ;
48
211f7ddb 49 @Override
08fe2e33
NR
50 public String toString() {
51 return super.toString().toLowerCase();
52 }
53
54 /**
55 * A description of this output type.
56 *
4d205683
NR
57 * @param longDesc
58 * TRUE for the long description, FALSE for the short one
59 *
08fe2e33
NR
60 * @return the description
61 */
4d205683
NR
62 public String getDesc(boolean longDesc) {
63 StringId id = longDesc ? StringId.OUTPUT_DESC
64 : StringId.OUTPUT_DESC_SHORT;
65
d66deb8d 66 String desc = Instance.getInstance().getTrans().getStringX(id, this.name());
08fe2e33
NR
67
68 if (desc == null) {
d66deb8d 69 desc = Instance.getInstance().getTrans().getString(id, this.toString());
08fe2e33
NR
70 }
71
fee80815 72 if (desc == null || desc.isEmpty()) {
b2612f9d
NR
73 desc = this.toString();
74 }
75
08fe2e33
NR
76 return desc;
77 }
78
2206ef66
NR
79 /**
80 * The default extension to add to the output files.
81 *
10d558d2 82 * @param readerTarget
f7460e4c
NR
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
10d558d2 87 *
2206ef66
NR
88 * @return the extension
89 */
10d558d2 90 public String getDefaultExtension(boolean readerTarget) {
925298fd 91 BasicOutput output = BasicOutput.getOutput(this, false, false);
2206ef66 92 if (output != null) {
10d558d2 93 return output.getDefaultExtension(readerTarget);
2206ef66
NR
94 }
95
96 return null;
97 }
98
08fe2e33 99 /**
0efd25e3
NR
100 * Call {@link OutputType#valueOf(String)} after conversion to upper
101 * case.
08fe2e33
NR
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 /**
0efd25e3 114 * Call {@link OutputType#valueOf(String)} after conversion to upper
e604986c 115 * case but return def for NULL and empty instead of raising an
0efd25e3 116 * exception.
08fe2e33
NR
117 *
118 * @param typeName
119 * the possible type name
e604986c
NR
120 * @param def
121 * the default value
08fe2e33
NR
122 *
123 * @return NULL or the type
124 */
e604986c 125 public static OutputType valueOfNullOkUC(String typeName, OutputType def) {
a6395bef 126 if (typeName == null || typeName.isEmpty()) {
e604986c 127 return def;
08fe2e33
NR
128 }
129
130 return OutputType.valueOfUC(typeName);
131 }
132
133 /**
0efd25e3 134 * Call {@link OutputType#valueOf(String)} after conversion to upper
e604986c 135 * case but return def in case of error instead of raising an exception.
08fe2e33
NR
136 *
137 * @param typeName
138 * the possible type name
e604986c
NR
139 * @param def
140 * the default value
08fe2e33
NR
141 *
142 * @return NULL or the type
143 */
e604986c 144 public static OutputType valueOfAllOkUC(String typeName, OutputType def) {
08fe2e33
NR
145 try {
146 return OutputType.valueOfUC(typeName);
147 } catch (Exception e) {
e604986c 148 return def;
08fe2e33
NR
149 }
150 }
151 }
152
153 /** The creator name (this program, by me!) */
3f85553e
NR
154 static protected final String EPUB_CREATOR = "Fanfix "
155 + Version.getCurrentVersion() + " (by Niki)";
08fe2e33
NR
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;
bee7dffe
NR
164 private Progress storyPg;
165 private Progress chapPg;
08fe2e33
NR
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)
bee7dffe
NR
175 * @param pg
176 * the optional progress reporter
08fe2e33
NR
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 */
bee7dffe
NR
184 public File process(Story story, String target, Progress pg)
185 throws IOException {
186 storyPg = pg;
187
d5a7153c
NR
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 }
08fe2e33
NR
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 *
bee7dffe 220 *
08fe2e33
NR
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
5083ef84
NR
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
08fe2e33
NR
265 /**
266 * The output type.
267 *
268 * @param type
269 * the new type
0efd25e3
NR
270 * @param writeCover
271 * TRUE to enable the creation of a cover if possible
925298fd
NR
272 * @param writeInfo
273 * TRUE to enable the creation of a .info file
08fe2e33
NR
274 *
275 * @return this
276 */
925298fd
NR
277 protected BasicOutput setType(OutputType type, boolean writeInfo,
278 boolean writeCover) {
08fe2e33 279 this.type = type;
08fe2e33 280 this.writeInfo = writeInfo;
925298fd 281 this.writeCover = writeCover;
08fe2e33
NR
282
283 return this;
284 }
285
286 /**
287 * The default extension to add to the output files.
08fe2e33 288 *
10d558d2 289 * @param readerTarget
f7460e4c
NR
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
10d558d2 294 *
08fe2e33
NR
295 * @return the extension
296 */
211f7ddb
NR
297 public String getDefaultExtension(
298 @SuppressWarnings("unused") boolean readerTarget) {
08fe2e33
NR
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 {
bee7dffe
NR
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
08fe2e33
NR
327 String chapterNameNum = String.format("%03d", 0);
328 String paragraphNumber = String.format("%04d", 0);
ecfb936e 329 imageName = paragraphNumber + "_" + chapterNameNum;
08fe2e33 330
fe999aa4 331 if (story.getMeta() != null) {
68686a37 332 story.getMeta().setType("" + getType());
fe999aa4 333 }
68686a37 334
276f95c6 335 if (isWriteCover()) {
08fe2e33
NR
336 InfoCover.writeCover(targetDir, targetName, story.getMeta());
337 }
276f95c6 338 if (isWriteInfo()) {
08fe2e33
NR
339 InfoCover.writeInfo(targetDir, targetName, story.getMeta());
340 }
341
bee7dffe
NR
342 storyPg.setProgress(1);
343
344 List<Progress> chapPgs = new ArrayList<Progress>(story.getChapters()
345 .size());
08fe2e33 346 for (Chapter chap : story) {
bee7dffe
NR
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;
08fe2e33
NR
359 }
360 writeStoryFooter(story);
bee7dffe
NR
361
362 storyPg.setProgress(storyPg.getMax());
363 storyPg = null;
08fe2e33
NR
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++);
16a81ef7 377 imageName = chapterNameNum + "_" + paragraphNumber;
08fe2e33
NR
378
379 writeChapterHeader(chap);
bee7dffe 380 int i = 1;
08fe2e33
NR
381 for (Paragraph para : chap) {
382 paragraphNumber = String.format("%04d", num++);
16a81ef7 383 imageName = chapterNameNum + "_" + paragraphNumber;
08fe2e33 384 writeParagraph(para);
bee7dffe
NR
385 if (chapPg != null) {
386 chapPg.setProgress(i++);
387 }
08fe2e33
NR
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
211f7ddb 398 @SuppressWarnings("unused")
08fe2e33
NR
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
925298fd
NR
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
08fe2e33
NR
520 * to the main target file
521 *
522 * @return the {@link BasicOutput}
523 */
925298fd
NR
524 public static BasicOutput getOutput(OutputType type, boolean writeInfo,
525 boolean writeCover) {
08fe2e33
NR
526 if (type != null) {
527 switch (type) {
528 case EPUB:
925298fd 529 return new Epub().setType(type, writeInfo, writeCover);
08fe2e33 530 case TEXT:
925298fd 531 return new Text().setType(type, writeInfo, true);
08fe2e33
NR
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:
925298fd 537 return new Cbz().setType(type, writeInfo, writeCover);
08fe2e33 538 case LATEX:
925298fd 539 return new LaTeX().setType(type, writeInfo, writeCover);
2206ef66 540 case HTML:
925298fd 541 return new Html().setType(type, writeInfo, writeCover);
08fe2e33
NR
542 }
543 }
544
545 return null;
546 }
547}