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