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