Commit | Line | Data |
---|---|---|
08fe2e33 NR |
1 | package be.nikiroo.fanfix.output; |
2 | ||
3d247bc3 | 3 | import java.io.BufferedWriter; |
08fe2e33 | 4 | import java.io.File; |
ecfb936e | 5 | import java.io.FileInputStream; |
3d247bc3 | 6 | import java.io.FileOutputStream; |
08fe2e33 NR |
7 | import java.io.IOException; |
8 | import java.io.InputStream; | |
ecfb936e | 9 | import java.io.OutputStream; |
3d247bc3 | 10 | import java.io.OutputStreamWriter; |
ecfb936e NR |
11 | import java.util.zip.ZipEntry; |
12 | import java.util.zip.ZipOutputStream; | |
08fe2e33 | 13 | |
08fe2e33 NR |
14 | import be.nikiroo.fanfix.Instance; |
15 | import be.nikiroo.fanfix.bundles.Config; | |
16 | import be.nikiroo.fanfix.bundles.StringId; | |
17 | import be.nikiroo.fanfix.data.Chapter; | |
18 | import be.nikiroo.fanfix.data.MetaData; | |
19 | import be.nikiroo.fanfix.data.Paragraph; | |
08fe2e33 | 20 | import be.nikiroo.fanfix.data.Paragraph.ParagraphType; |
3d247bc3 | 21 | import be.nikiroo.fanfix.data.Story; |
08fe2e33 NR |
22 | import be.nikiroo.utils.IOUtils; |
23 | import be.nikiroo.utils.StringUtils; | |
24 | ||
25 | class Epub extends BasicOutput { | |
26 | private File tmpDir; | |
3d247bc3 | 27 | private BufferedWriter writer; |
08fe2e33 NR |
28 | private boolean inDialogue = false; |
29 | private boolean inNormal = false; | |
30 | private File images; | |
16a81ef7 | 31 | private boolean nextParaIsCover = true; |
08fe2e33 NR |
32 | |
33 | @Override | |
34 | public File process(Story story, File targetDir, String targetName) | |
35 | throws IOException { | |
36 | String targetNameOrig = targetName; | |
10d558d2 | 37 | targetName += getDefaultExtension(false); |
08fe2e33 | 38 | |
d66deb8d | 39 | tmpDir = Instance.getInstance().getTempFiles().createTempDir("fanfic-reader-epub"); |
08fe2e33 NR |
40 | tmpDir.delete(); |
41 | ||
42 | if (!tmpDir.mkdir()) { | |
43 | throw new IOException( | |
44 | "Cannot create a temporary directory: no space left on device?"); | |
45 | } | |
46 | ||
276f95c6 NR |
47 | super.process(story, targetDir, targetNameOrig); |
48 | ||
2aac79c7 | 49 | File epub = null; |
ecfb936e | 50 | try { |
2aac79c7 NR |
51 | // "Originals" |
52 | File data = new File(tmpDir, "DATA"); | |
53 | data.mkdir(); | |
54 | BasicOutput.getOutput(OutputType.TEXT, isWriteInfo(), | |
55 | isWriteCover()).process(story, data, targetNameOrig); | |
56 | InfoCover.writeInfo(data, targetNameOrig, story.getMeta()); | |
57 | IOUtils.writeSmallFile(data, "version", "3.0"); | |
58 | ||
59 | // zip/epub | |
60 | epub = new File(targetDir, targetName); | |
61 | IOUtils.zip(tmpDir, epub, true); | |
62 | ||
63 | OutputStream out = new FileOutputStream(epub); | |
ecfb936e | 64 | try { |
2aac79c7 | 65 | ZipOutputStream zip = new ZipOutputStream(out); |
ecfb936e | 66 | try { |
2aac79c7 NR |
67 | // "mimetype" MUST be the first element and not compressed |
68 | zip.setLevel(ZipOutputStream.STORED); | |
69 | File mimetype = new File(tmpDir, "mimetype"); | |
70 | IOUtils.writeSmallFile(tmpDir, "mimetype", | |
71 | "application/epub+zip"); | |
72 | ZipEntry entry = new ZipEntry("mimetype"); | |
73 | entry.setExtra(new byte[] {}); | |
74 | zip.putNextEntry(entry); | |
75 | FileInputStream in = new FileInputStream(mimetype); | |
76 | try { | |
77 | IOUtils.write(in, zip); | |
78 | } finally { | |
79 | in.close(); | |
80 | } | |
81 | IOUtils.deltree(mimetype); | |
82 | zip.setLevel(ZipOutputStream.DEFLATED); | |
83 | // | |
84 | ||
85 | IOUtils.zip(zip, "", tmpDir, true); | |
ecfb936e | 86 | } finally { |
2aac79c7 | 87 | zip.close(); |
ecfb936e | 88 | } |
ecfb936e | 89 | } finally { |
2aac79c7 | 90 | out.close(); |
ecfb936e NR |
91 | } |
92 | } finally { | |
2aac79c7 NR |
93 | IOUtils.deltree(tmpDir); |
94 | tmpDir = null; | |
ecfb936e NR |
95 | } |
96 | ||
08fe2e33 NR |
97 | return epub; |
98 | } | |
99 | ||
100 | @Override | |
10d558d2 | 101 | public String getDefaultExtension(boolean readerTarget) { |
08fe2e33 NR |
102 | return ".epub"; |
103 | } | |
104 | ||
105 | @Override | |
106 | protected void writeStoryHeader(Story story) throws IOException { | |
107 | File ops = new File(tmpDir, "OPS"); | |
108 | ops.mkdirs(); | |
109 | File css = new File(ops, "css"); | |
110 | css.mkdirs(); | |
111 | images = new File(ops, "images"); | |
112 | images.mkdirs(); | |
113 | File metaInf = new File(tmpDir, "META-INF"); | |
114 | metaInf.mkdirs(); | |
115 | ||
08fe2e33 | 116 | // META-INF |
ecfb936e | 117 | String containerContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
08fe2e33 NR |
118 | + "<container xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\" version=\"1.0\">\n" |
119 | + "\t<rootfiles>\n" | |
120 | + "\t\t<rootfile full-path=\"OPS/epb.opf\" media-type=\"application/oebps-package+xml\"/>\n" | |
121 | + "\t</rootfiles>\n" + "</container>\n"; | |
122 | ||
123 | IOUtils.writeSmallFile(metaInf, "container.xml", containerContent); | |
124 | ||
125 | // OPS/css | |
126 | InputStream inStyle = getClass().getResourceAsStream("epub.style.css"); | |
127 | if (inStyle == null) { | |
128 | throw new IOException("Cannot find style.css resource"); | |
129 | } | |
130 | try { | |
131 | IOUtils.write(inStyle, new File(css, "style.css")); | |
132 | } finally { | |
133 | inStyle.close(); | |
134 | } | |
135 | ||
136 | // OPS/images | |
137 | if (story.getMeta() != null && story.getMeta().getCover() != null) { | |
ecfb936e | 138 | File file = new File(images, "cover"); |
43c75fb7 | 139 | try { |
d66deb8d | 140 | Instance.getInstance().getCache().saveAsImage(story.getMeta().getCover(), file, true); |
43c75fb7 | 141 | } catch (Exception e) { |
d66deb8d | 142 | Instance.getInstance().getTraceHandler().error(e); |
43c75fb7 | 143 | } |
08fe2e33 NR |
144 | } |
145 | ||
146 | // OPS/* except chapters | |
147 | IOUtils.writeSmallFile(ops, "epb.ncx", generateNcx(story)); | |
148 | IOUtils.writeSmallFile(ops, "epb.opf", generateOpf(story)); | |
ecfb936e | 149 | IOUtils.writeSmallFile(ops, "title.xhtml", generateTitleXml(story)); |
08fe2e33 NR |
150 | |
151 | // Resume | |
152 | if (story.getMeta() != null && story.getMeta().getResume() != null) { | |
153 | writeChapter(story.getMeta().getResume()); | |
154 | } | |
155 | } | |
156 | ||
157 | @Override | |
158 | protected void writeChapterHeader(Chapter chap) throws IOException { | |
159 | String filename = String.format("%s%03d%s", "chapter-", | |
ecfb936e | 160 | chap.getNumber(), ".xhtml"); |
3d247bc3 NR |
161 | writer = new BufferedWriter(new OutputStreamWriter( |
162 | new FileOutputStream(new File(tmpDir + File.separator + "OPS", | |
163 | filename)), "UTF-8")); | |
08fe2e33 NR |
164 | inDialogue = false; |
165 | inNormal = false; | |
166 | try { | |
167 | String title = "Chapter " + chap.getNumber(); | |
168 | String nameOrNum = Integer.toString(chap.getNumber()); | |
169 | if (chap.getName() != null && !chap.getName().isEmpty()) { | |
170 | title += ": " + chap.getName(); | |
171 | nameOrNum = chap.getName(); | |
172 | } | |
173 | ||
ecfb936e NR |
174 | writer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
175 | writer.append("\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"); | |
176 | writer.append("\n<html xmlns=\"http://www.w3.org/1999/xhtml\">"); | |
08fe2e33 NR |
177 | writer.write("\n<head>"); |
178 | writer.write("\n <title>" + StringUtils.xmlEscape(title) | |
179 | + "</title>"); | |
180 | writer.write("\n <link rel='stylesheet' href='css/style.css' type='text/css'/>"); | |
181 | writer.write("\n</head>"); | |
182 | writer.write("\n<body>"); | |
183 | writer.write("\n <h2>"); | |
184 | writer.write("\n <span class='chap'>Chapter <span class='chapnumber'>" | |
185 | + chap.getNumber() + "</span>:</span> "); | |
186 | writer.write("\n <span class='chaptitle'>" | |
187 | + StringUtils.xmlEscape(nameOrNum) + "</span>"); | |
188 | writer.write("\n </h2>"); | |
189 | writer.write("\n "); | |
190 | writer.write("\n <div class='chapter_content'>\n"); | |
191 | } catch (Exception e) { | |
192 | writer.close(); | |
193 | throw new IOException(e); | |
194 | } | |
195 | } | |
196 | ||
197 | @Override | |
198 | protected void writeChapterFooter(Chapter chap) throws IOException { | |
199 | try { | |
200 | if (inDialogue) { | |
201 | writer.write(" </div>\n"); | |
202 | inDialogue = false; | |
203 | } | |
204 | if (inNormal) { | |
205 | writer.write(" </div>\n"); | |
206 | inNormal = false; | |
207 | } | |
208 | writer.write(" </div>\n</body>\n</html>\n"); | |
209 | } finally { | |
210 | writer.close(); | |
211 | writer = null; | |
212 | } | |
213 | } | |
214 | ||
215 | @Override | |
216 | protected void writeParagraphHeader(Paragraph para) throws IOException { | |
217 | if (para.getType() == ParagraphType.QUOTE && !inDialogue) { | |
218 | writer.write(" <div class='dialogues'>\n"); | |
219 | inDialogue = true; | |
220 | } else if (para.getType() != ParagraphType.QUOTE && inDialogue) { | |
221 | writer.write(" </div>\n"); | |
222 | inDialogue = false; | |
223 | } | |
224 | ||
225 | if (para.getType() == ParagraphType.NORMAL && !inNormal) { | |
226 | writer.write(" <div class='normals'>\n"); | |
227 | inNormal = true; | |
228 | } else if (para.getType() != ParagraphType.NORMAL && inNormal) { | |
229 | writer.write(" </div>\n"); | |
230 | inNormal = false; | |
231 | } | |
232 | ||
233 | switch (para.getType()) { | |
234 | case BLANK: | |
235 | writer.write(" <div class='blank'></div>"); | |
236 | break; | |
237 | case BREAK: | |
f977d05b | 238 | writer.write(" <hr class='break'/>"); |
08fe2e33 NR |
239 | break; |
240 | case NORMAL: | |
241 | writer.write(" <span class='normal'>"); | |
242 | break; | |
243 | case QUOTE: | |
244 | writer.write(" <div class='dialogue'>— "); | |
245 | break; | |
246 | case IMAGE: | |
247 | File file = new File(images, getCurrentImageBestName(false)); | |
d66deb8d | 248 | Instance.getInstance().getCache().saveAsImage(para.getContentImage(), file, nextParaIsCover); |
ecfb936e | 249 | writer.write(" <img alt='page image' class='page-image' src='images/" |
08fe2e33 NR |
250 | + getCurrentImageBestName(false) + "'/>"); |
251 | break; | |
252 | } | |
16a81ef7 NR |
253 | |
254 | nextParaIsCover = false; | |
08fe2e33 NR |
255 | } |
256 | ||
257 | @Override | |
258 | protected void writeParagraphFooter(Paragraph para) throws IOException { | |
259 | switch (para.getType()) { | |
260 | case NORMAL: | |
261 | writer.write("</span>\n"); | |
262 | break; | |
263 | case QUOTE: | |
264 | writer.write("</div>\n"); | |
265 | break; | |
266 | default: | |
267 | writer.write("\n"); | |
268 | break; | |
269 | } | |
270 | } | |
271 | ||
272 | @Override | |
273 | protected void writeTextLine(ParagraphType type, String line) | |
274 | throws IOException { | |
275 | switch (type) { | |
276 | case QUOTE: | |
277 | case NORMAL: | |
278 | writer.write(decorateText(StringUtils.xmlEscape(line))); | |
279 | break; | |
280 | default: | |
281 | break; | |
282 | } | |
283 | } | |
284 | ||
285 | @Override | |
286 | protected String enbold(String word) { | |
287 | return "<strong>" + word + "</strong>"; | |
288 | } | |
289 | ||
290 | @Override | |
291 | protected String italize(String word) { | |
292 | return "<emph>" + word + "</emph>"; | |
293 | } | |
294 | ||
295 | private String generateNcx(Story story) { | |
296 | StringBuilder builder = new StringBuilder(); | |
297 | ||
298 | String title = ""; | |
299 | String uuid = ""; | |
300 | String author = ""; | |
301 | if (story.getMeta() != null) { | |
302 | MetaData meta = story.getMeta(); | |
303 | uuid = meta.getUuid(); | |
304 | author = meta.getAuthor(); | |
305 | title = meta.getTitle(); | |
306 | } | |
307 | ||
ecfb936e | 308 | builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
08fe2e33 NR |
309 | builder.append("\n<!DOCTYPE ncx"); |
310 | builder.append("\nPUBLIC \"-//NISO//DTD ncx 2005-1//EN\" \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">"); | |
311 | builder.append("\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">"); | |
312 | builder.append("\n <head>"); | |
313 | builder.append("\n <!--The following four metadata items are required for all"); | |
314 | builder.append("\n NCX documents, including those conforming to the relaxed"); | |
315 | builder.append("\n constraints of OPS 2.0-->"); | |
316 | builder.append("\n <meta name=\"dtb:uid\" content=\"" | |
317 | + StringUtils.xmlEscapeQuote(uuid) + "\"/>"); | |
318 | builder.append("\n <meta name=\"dtb:depth\" content=\"1\"/>"); | |
319 | builder.append("\n <meta name=\"dtb:totalPageCount\" content=\"0\"/>"); | |
320 | builder.append("\n <meta name=\"dtb:maxPageNumber\" content=\"0\"/>"); | |
321 | builder.append("\n <meta name=\"epub-creator\" content=\"" | |
322 | + StringUtils.xmlEscapeQuote(EPUB_CREATOR) + "\"/>"); | |
323 | builder.append("\n </head>"); | |
324 | builder.append("\n <docTitle>"); | |
325 | builder.append("\n <text>" + StringUtils.xmlEscape(title) + "</text>"); | |
326 | builder.append("\n </docTitle>"); | |
327 | builder.append("\n <docAuthor>"); | |
328 | ||
329 | builder.append("\n <text>" + StringUtils.xmlEscape(author) + "</text>"); | |
330 | builder.append("\n </docAuthor>"); | |
331 | builder.append("\n <navMap>"); | |
332 | builder.append("\n <navPoint id=\"navpoint-1\" playOrder=\"1\">"); | |
333 | builder.append("\n <navLabel>"); | |
334 | builder.append("\n <text>Title Page</text>"); | |
335 | builder.append("\n </navLabel>"); | |
ecfb936e | 336 | builder.append("\n <content src=\"title.xhtml\"/>"); |
08fe2e33 NR |
337 | builder.append("\n </navPoint>"); |
338 | ||
339 | int navPoint = 2; // 1 is above | |
340 | ||
341 | if (story.getMeta() != null & story.getMeta().getResume() != null) { | |
342 | Chapter chap = story.getMeta().getResume(); | |
343 | generateNcx(chap, builder, navPoint++); | |
344 | } | |
345 | ||
346 | for (Chapter chap : story) { | |
347 | generateNcx(chap, builder, navPoint++); | |
348 | } | |
349 | ||
350 | builder.append("\n </navMap>"); | |
351 | builder.append("\n</ncx>\n"); | |
352 | ||
353 | return builder.toString(); | |
354 | } | |
355 | ||
356 | private void generateNcx(Chapter chap, StringBuilder builder, int navPoint) { | |
357 | String name; | |
358 | if (chap.getName() != null && !chap.getName().isEmpty()) { | |
d66deb8d NR |
359 | name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(), |
360 | chap.getName()); | |
08fe2e33 | 361 | } else { |
d66deb8d | 362 | name = Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber()); |
08fe2e33 NR |
363 | } |
364 | ||
365 | String nnn = String.format("%03d", (navPoint - 2)); | |
366 | ||
367 | builder.append("\n <navPoint id=\"navpoint-" + navPoint | |
368 | + "\" playOrder=\"" + navPoint + "\">"); | |
369 | builder.append("\n <navLabel>"); | |
370 | builder.append("\n <text>" + name + "</text>"); | |
371 | builder.append("\n </navLabel>"); | |
ecfb936e | 372 | builder.append("\n <content src=\"chapter-" + nnn + ".xhtml\"/>"); |
08fe2e33 NR |
373 | builder.append("\n </navPoint>\n"); |
374 | } | |
375 | ||
376 | private String generateOpf(Story story) { | |
377 | StringBuilder builder = new StringBuilder(); | |
378 | ||
379 | String title = ""; | |
380 | String uuid = ""; | |
381 | String author = ""; | |
382 | String date = ""; | |
383 | String publisher = ""; | |
384 | String subject = ""; | |
385 | String source = ""; | |
386 | String lang = ""; | |
387 | if (story.getMeta() != null) { | |
388 | MetaData meta = story.getMeta(); | |
389 | title = meta.getTitle(); | |
390 | uuid = meta.getUuid(); | |
391 | author = meta.getAuthor(); | |
392 | date = meta.getDate(); | |
393 | publisher = meta.getPublisher(); | |
394 | subject = meta.getSubject(); | |
395 | source = meta.getSource(); | |
396 | lang = meta.getLang(); | |
397 | } | |
398 | ||
ecfb936e NR |
399 | builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
400 | builder.append("\n<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"BookId\" version=\"2.0\">"); | |
08fe2e33 NR |
401 | builder.append("\n <metadata xmlns:opf=\"http://www.idpf.org/2007/opf\""); |
402 | builder.append("\n xmlns:dc=\"http://purl.org/dc/elements/1.1/\">"); | |
403 | builder.append("\n <dc:title>" + StringUtils.xmlEscape(title) | |
404 | + "</dc:title>"); | |
405 | builder.append("\n <dc:creator opf:role=\"aut\" opf:file-as=\"" | |
406 | + StringUtils.xmlEscapeQuote(author) + "\">" | |
407 | + StringUtils.xmlEscape(author) + "</dc:creator>"); | |
408 | builder.append("\n <dc:date opf:event=\"original-publication\">" | |
409 | + StringUtils.xmlEscape(date) + "</dc:date>"); | |
410 | builder.append("\n <dc:publisher>" | |
411 | + StringUtils.xmlEscape(publisher) + "</dc:publisher>"); | |
412 | builder.append("\n <dc:date opf:event=\"epub-publication\"></dc:date>"); | |
413 | builder.append("\n <dc:subject>" + StringUtils.xmlEscape(subject) | |
414 | + "</dc:subject>"); | |
415 | builder.append("\n <dc:source>" + StringUtils.xmlEscape(source) | |
416 | + "</dc:source>"); | |
417 | builder.append("\n <dc:rights>Not for commercial use.</dc:rights>"); | |
ecfb936e | 418 | builder.append("\n <dc:identifier id=\"BookId\" opf:scheme=\"URI\">" |
08fe2e33 NR |
419 | + StringUtils.xmlEscape(uuid) + "</dc:identifier>"); |
420 | builder.append("\n <dc:language>" + StringUtils.xmlEscape(lang) | |
421 | + "</dc:language>"); | |
422 | builder.append("\n </metadata>"); | |
423 | builder.append("\n <manifest>"); | |
424 | builder.append("\n <!-- Content Documents -->"); | |
ecfb936e | 425 | builder.append("\n <item id=\"titlepage\" href=\"title.xhtml\" media-type=\"application/xhtml+xml\"/>"); |
08fe2e33 NR |
426 | for (int i = 0; i <= story.getChapters().size(); i++) { |
427 | String name = String.format("%s%03d", "chapter-", i); | |
428 | builder.append("\n <item id=\"" | |
429 | + StringUtils.xmlEscapeQuote(name) + "\" href=\"" | |
430 | + StringUtils.xmlEscapeQuote(name) | |
ecfb936e | 431 | + ".xhtml\" media-type=\"application/xhtml+xml\"/>"); |
08fe2e33 NR |
432 | } |
433 | ||
434 | builder.append("\n <!-- CSS Style Sheets -->"); | |
435 | builder.append("\n <item id=\"style-css\" href=\"css/style.css\" media-type=\"text/css\"/>"); | |
436 | ||
437 | builder.append("\n <!-- Images -->"); | |
438 | ||
439 | if (story.getMeta() != null && story.getMeta().getCover() != null) { | |
d66deb8d | 440 | String format = Instance.getInstance().getConfig() |
43c75fb7 NR |
441 | .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER) |
442 | .toLowerCase(); | |
08fe2e33 NR |
443 | builder.append("\n <item id=\"cover\" href=\"images/cover." |
444 | + format + "\" media-type=\"image/png\"/>"); | |
445 | } | |
446 | ||
447 | builder.append("\n <!-- NCX -->"); | |
448 | builder.append("\n <item id=\"ncx\" href=\"epb.ncx\" media-type=\"application/x-dtbncx+xml\"/>"); | |
449 | builder.append("\n </manifest>"); | |
450 | builder.append("\n <spine toc=\"ncx\">"); | |
451 | builder.append("\n <itemref idref=\"titlepage\" linear=\"yes\"/>"); | |
452 | for (int i = 0; i <= story.getChapters().size(); i++) { | |
453 | String name = String.format("%s%03d", "chapter-", i); | |
454 | builder.append("\n <itemref idref=\"" | |
455 | + StringUtils.xmlEscapeQuote(name) + "\" linear=\"yes\"/>"); | |
456 | } | |
457 | builder.append("\n </spine>"); | |
458 | builder.append("\n</package>\n"); | |
459 | ||
460 | return builder.toString(); | |
461 | } | |
462 | ||
463 | private String generateTitleXml(Story story) { | |
464 | StringBuilder builder = new StringBuilder(); | |
465 | ||
466 | String title = ""; | |
467 | String tags = ""; | |
468 | String author = ""; | |
469 | if (story.getMeta() != null) { | |
470 | MetaData meta = story.getMeta(); | |
471 | title = meta.getTitle(); | |
472 | if (meta.getTags() != null) { | |
473 | for (String tag : meta.getTags()) { | |
474 | if (!tags.isEmpty()) { | |
475 | tags += ", "; | |
476 | } | |
477 | tags += tag; | |
478 | } | |
479 | ||
480 | if (!tags.isEmpty()) { | |
481 | tags = "(" + tags + ")"; | |
482 | } | |
483 | } | |
484 | author = meta.getAuthor(); | |
485 | } | |
486 | ||
d66deb8d | 487 | String format = Instance.getInstance().getConfig() |
13fdb89a | 488 | .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); |
08fe2e33 | 489 | |
ecfb936e NR |
490 | builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
491 | builder.append("\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"); | |
492 | builder.append("\n<html xmlns=\"http://www.w3.org/1999/xhtml\">"); | |
08fe2e33 NR |
493 | builder.append("\n<head>"); |
494 | builder.append("\n <title>" + StringUtils.xmlEscape(title) + "</title>"); | |
495 | builder.append("\n <link rel=\"stylesheet\" href=\"css/style.css\" type=\"text/css\"/>"); | |
496 | builder.append("\n</head>"); | |
497 | builder.append("\n<body>"); | |
498 | builder.append("\n <div class=\"titlepage\">"); | |
499 | builder.append("\n <h1>" + StringUtils.xmlEscape(title) + "</h1>"); | |
500 | builder.append("\n <div class=\"type\">" | |
501 | + StringUtils.xmlEscape(tags) + "</div>"); | |
502 | builder.append("\n <div class=\"cover\">"); | |
ecfb936e NR |
503 | builder.append("\n <img alt=\"cover image\" src=\"images/cover." |
504 | + format + "\"></img>"); | |
08fe2e33 NR |
505 | builder.append("\n </div>"); |
506 | builder.append("\n <div class=\"author\">" | |
507 | + StringUtils.xmlEscape(author) + "</div>"); | |
508 | builder.append("\n </div>"); | |
509 | builder.append("\n</body>"); | |
510 | builder.append("\n</html>\n"); | |
511 | ||
512 | return builder.toString(); | |
513 | } | |
514 | } |