1 package org.apache.maven.doxia.docrenderer.pdf.itext;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.text.SimpleDateFormat;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.Iterator;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import javax.xml.transform.OutputKeys;
43 import javax.xml.transform.Transformer;
44 import javax.xml.transform.TransformerConfigurationException;
45 import javax.xml.transform.TransformerException;
46 import javax.xml.transform.TransformerFactory;
47 import javax.xml.transform.dom.DOMSource;
48 import javax.xml.transform.stream.StreamResult;
49 import javax.xml.transform.stream.StreamSource;
50
51 import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
52 import org.apache.maven.doxia.docrenderer.DocumentRendererException;
53 import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer;
54 import org.apache.maven.doxia.document.DocumentCover;
55 import org.apache.maven.doxia.document.DocumentMeta;
56 import org.apache.maven.doxia.document.DocumentModel;
57 import org.apache.maven.doxia.document.DocumentTOCItem;
58 import org.apache.maven.doxia.module.itext.ITextSink;
59 import org.apache.maven.doxia.module.itext.ITextSinkFactory;
60 import org.apache.maven.doxia.module.itext.ITextUtil;
61 import org.apache.maven.doxia.module.site.SiteModule;
62 import org.apache.xml.utils.DefaultErrorHandler;
63 import org.codehaus.plexus.util.IOUtil;
64 import org.codehaus.plexus.util.StringUtils;
65 import org.codehaus.plexus.util.WriterFactory;
66 import org.w3c.dom.DOMException;
67 import org.w3c.dom.Document;
68 import org.w3c.dom.Node;
69 import org.xml.sax.SAXException;
70
71 import com.lowagie.text.ElementTags;
72
73
74
75
76
77
78
79
80
81
82 public class ITextPdfRenderer
83 extends AbstractPdfRenderer
84 {
85
86 private static final String XSLT_RESOURCE = "TOC.xslt";
87
88
89 private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
90
91
92 private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
93
94
95 private static final DocumentBuilder DOCUMENT_BUILDER;
96
97 static
98 {
99 TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() );
100
101 try
102 {
103 DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
104 }
105 catch ( ParserConfigurationException e )
106 {
107 throw new RuntimeException( "Error building document :" + e.getMessage() );
108 }
109 }
110
111
112 public void generatePdf( File inputFile, File pdfFile )
113 throws DocumentRendererException
114 {
115 if ( getLogger().isDebugEnabled() )
116 {
117 getLogger().debug( "Generating : " + pdfFile );
118 }
119
120 try
121 {
122 ITextUtil.writePdf( new FileInputStream( inputFile ), new FileOutputStream( pdfFile ) );
123 }
124 catch ( IOException e )
125 {
126 throw new DocumentRendererException( "Cannot create PDF from " + inputFile + ": " + e.getMessage(), e );
127 }
128 catch ( RuntimeException e )
129 {
130 throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage(), e );
131 }
132 }
133
134
135 public void render( Map<String, SiteModule> filesToProcess, File outputDirectory, DocumentModel documentModel )
136 throws DocumentRendererException, IOException
137 {
138 render( filesToProcess, outputDirectory, documentModel, null );
139 }
140
141
142 public void render( Map<String, SiteModule> filesToProcess, File outputDirectory, DocumentModel documentModel,
143 DocumentRendererContext context )
144 throws DocumentRendererException, IOException
145 {
146
147 copyResources( outputDirectory );
148
149 if ( documentModel == null )
150 {
151 getLogger().debug( "No document model, generating all documents individually." );
152
153 renderIndividual( filesToProcess, outputDirectory, context );
154 return;
155 }
156
157 String outputName = getOutputName( documentModel );
158
159 File outputITextFile = new File( outputDirectory, outputName + ".xml" );
160 if ( !outputITextFile.getParentFile().exists() )
161 {
162 outputITextFile.getParentFile().mkdirs();
163 }
164
165 File pdfOutputFile = new File( outputDirectory, outputName + ".pdf" );
166 if ( !pdfOutputFile.getParentFile().exists() )
167 {
168 pdfOutputFile.getParentFile().mkdirs();
169 }
170
171 List<File> iTextFiles;
172 if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) )
173 {
174 getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." );
175
176 iTextFiles = parseAllFiles( filesToProcess, outputDirectory, context );
177 }
178 else
179 {
180 getLogger().debug( "Using TOC defined in the document descriptor." );
181
182 iTextFiles = parseTOCFiles( outputDirectory, documentModel, context );
183 }
184
185 String generateTOC =
186 ( context != null && context.get( "generateTOC" ) != null ? context.get( "generateTOC" ).toString()
187 : "start" );
188
189 File iTextFile = new File( outputDirectory, outputName + ".xml" );
190 File iTextOutput = new File( outputDirectory, outputName + "." + getOutputExtension() );
191 Document document = generateDocument( iTextFiles );
192 transform( documentModel, document, iTextFile, generateTOC );
193 generatePdf( iTextFile, iTextOutput );
194 }
195
196
197 public void renderIndividual( Map<String, SiteModule> filesToProcess, File outputDirectory )
198 throws DocumentRendererException, IOException
199 {
200 renderIndividual( filesToProcess, outputDirectory, null );
201 }
202
203
204 public void renderIndividual( Map<String, SiteModule> filesToProcess, File outputDirectory,
205 DocumentRendererContext context )
206 throws DocumentRendererException, IOException
207 {
208 for ( Map.Entry<String, SiteModule> entry : filesToProcess.entrySet() )
209 {
210 String key = entry.getKey();
211 SiteModule module = entry.getValue();
212 File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key );
213
214 String output = key;
215 String lowerCaseExtension = module.getExtension().toLowerCase( Locale.ENGLISH );
216 if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 )
217 {
218 output =
219 output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) );
220 }
221
222 File outputITextFile = new File( outputDirectory, output + ".xml" );
223 if ( !outputITextFile.getParentFile().exists() )
224 {
225 outputITextFile.getParentFile().mkdirs();
226 }
227
228 File pdfOutputFile = new File( outputDirectory, output + ".pdf" );
229 if ( !pdfOutputFile.getParentFile().exists() )
230 {
231 pdfOutputFile.getParentFile().mkdirs();
232 }
233
234 parse( fullDoc, module, outputITextFile, context );
235
236 generatePdf( outputITextFile, pdfOutputFile );
237 }
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 private void parse( File fullDoc, SiteModule module, File iTextFile, DocumentRendererContext context )
255 throws DocumentRendererException, IOException
256 {
257 if ( getLogger().isDebugEnabled() )
258 {
259 getLogger().debug( "Parsing file " + fullDoc.getAbsolutePath() );
260 }
261
262 System.setProperty( "itext.basedir", iTextFile.getParentFile().getAbsolutePath() );
263
264 Writer writer = null;
265 ITextSink sink = null;
266 try
267 {
268 writer = WriterFactory.newXmlWriter( iTextFile );
269 sink = (ITextSink) new ITextSinkFactory().createSink( writer );
270
271 sink.setClassLoader( new URLClassLoader( new URL[] { iTextFile.getParentFile().toURI().toURL() } ) );
272
273 parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context );
274 }
275 finally
276 {
277 if ( sink != null )
278 {
279 sink.flush();
280 sink.close();
281 }
282 IOUtil.close( writer );
283 System.getProperties().remove( "itext.basedir" );
284 }
285 }
286
287
288
289
290
291
292
293
294
295 private Document generateDocument( List<File> iTextFiles )
296 throws DocumentRendererException, IOException
297 {
298 Document document = DOCUMENT_BUILDER.newDocument();
299 document.appendChild( document.createElement( ElementTags.ITEXT ) );
300
301 for ( File iTextFile : iTextFiles )
302 {
303 Document iTextDocument;
304
305 try
306 {
307 iTextDocument = DOCUMENT_BUILDER.parse( iTextFile );
308 }
309 catch ( SAXException e )
310 {
311 throw new DocumentRendererException( "SAX Error : " + e.getMessage() );
312 }
313
314
315 Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 );
316
317 try
318 {
319 document.getDocumentElement().appendChild( document.importNode( chapter, true ) );
320 }
321 catch ( DOMException e )
322 {
323 throw new DocumentRendererException( "Error appending chapter for "
324 + iTextFile + " : " + e.getMessage() );
325 }
326 }
327
328 return document;
329 }
330
331
332
333
334
335
336
337 private Transformer initTransformer()
338 throws DocumentRendererException
339 {
340 try
341 {
342 Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( ITextPdfRenderer.class
343 .getResourceAsStream( XSLT_RESOURCE ) ) );
344
345 transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() );
346
347 transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" );
348
349 transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
350
351 transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
352
353 transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
354
355
356
357 return transformer;
358 }
359 catch ( TransformerConfigurationException e )
360 {
361 throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
362 + e.getMessage() );
363 }
364 catch ( IllegalArgumentException e )
365 {
366 throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
367 + e.getMessage() );
368 }
369 }
370
371
372
373
374
375
376
377
378
379 private void addTransformerParameters( Transformer transformer, DocumentModel documentModel, File iTextFile,
380 String generateTOC )
381 {
382 if ( documentModel == null )
383 {
384 return;
385 }
386
387
388 addTransformerParameter( transformer, "toc.position", generateTOC );
389
390
391 boolean hasNullMeta = false;
392 if ( documentModel.getMeta() == null )
393 {
394 hasNullMeta = true;
395 documentModel.setMeta( new DocumentMeta() );
396 }
397 addTransformerParameter( transformer, "meta.author", documentModel.getMeta().getAllAuthorNames(),
398 System.getProperty( "user.name", "null" ) );
399 addTransformerParameter( transformer, "meta.creator", documentModel.getMeta().getCreator(),
400 System.getProperty( "user.name", "null" ) );
401
402 SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" );
403 addTransformerParameter( transformer, "meta.creationdate", documentModel.getMeta().getCreationdate(),
404 sdf.format( new Date() ) );
405 addTransformerParameter( transformer, "meta.keywords", documentModel.getMeta().getAllKeyWords() );
406 addTransformerParameter( transformer, "meta.pagesize", documentModel.getMeta().getPageSize(),
407 ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) );
408 addTransformerParameter( transformer, "meta.producer", documentModel.getMeta().getGenerator(),
409 "Apache Doxia iText" );
410 addTransformerParameter( transformer, "meta.subject", documentModel.getMeta().getSubject(),
411 ( documentModel.getMeta().getTitle() != null ? documentModel.getMeta().getTitle()
412 : "" ) );
413 addTransformerParameter( transformer, "meta.title", documentModel.getMeta().getTitle() );
414 if ( hasNullMeta )
415 {
416 documentModel.setMeta( null );
417 }
418
419
420 boolean hasNullCover = false;
421 if ( documentModel.getCover() == null )
422 {
423 hasNullCover = true;
424 documentModel.setCover( new DocumentCover() );
425 }
426 addTransformerParameter( transformer, "cover.author", documentModel.getCover().getAllAuthorNames(),
427 System.getProperty( "user.name", "null" ) );
428 String companyLogo = getLogoURL( documentModel.getCover().getCompanyLogo(), iTextFile.getParentFile() );
429 addTransformerParameter( transformer, "cover.companyLogo", companyLogo );
430 addTransformerParameter( transformer, "cover.companyName", documentModel.getCover().getCompanyName() );
431 if ( documentModel.getCover().getCoverdate() == null )
432 {
433 documentModel.getCover().setCoverDate( new Date() );
434 addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() );
435 documentModel.getCover().setCoverDate( null );
436 }
437 else
438 {
439 addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() );
440 }
441 addTransformerParameter( transformer, "cover.subtitle", documentModel.getCover().getCoverSubTitle() );
442 addTransformerParameter( transformer, "cover.title", documentModel.getCover().getCoverTitle() );
443 addTransformerParameter( transformer, "cover.type", documentModel.getCover().getCoverType() );
444 addTransformerParameter( transformer, "cover.version", documentModel.getCover().getCoverVersion() );
445 String projectLogo = getLogoURL( documentModel.getCover().getProjectLogo(), iTextFile.getParentFile() );
446 addTransformerParameter( transformer, "cover.projectLogo", projectLogo );
447 addTransformerParameter( transformer, "cover.projectName", documentModel.getCover().getProjectName() );
448 if ( hasNullCover )
449 {
450 documentModel.setCover( null );
451 }
452 }
453
454
455
456
457
458
459
460
461 private void addTransformerParameter( Transformer transformer, String name, String value, String defaultValue )
462 {
463 if ( StringUtils.isEmpty( value ) )
464 {
465 addTransformerParameter( transformer, name, defaultValue );
466 }
467 else
468 {
469 addTransformerParameter( transformer, name, value );
470 }
471 }
472
473
474
475
476
477
478
479 private void addTransformerParameter( Transformer transformer, String name, String value )
480 {
481 if ( StringUtils.isEmpty( value ) )
482 {
483 return;
484 }
485
486 transformer.setParameter( name, value );
487 }
488
489
490
491
492
493
494
495
496
497
498 private void transform( DocumentModel documentModel, Document document, File iTextFile, String generateTOC )
499 throws DocumentRendererException
500 {
501 Transformer transformer = initTransformer();
502
503 addTransformerParameters( transformer, documentModel, iTextFile, generateTOC );
504
505
506 Writer writer = null;
507 try
508 {
509 writer = WriterFactory.newXmlWriter( iTextFile );
510 transformer.transform( new DOMSource( document ), new StreamResult( writer ) );
511 }
512 catch ( TransformerException e )
513 {
514 throw new DocumentRendererException(
515 "Error transforming Document " + document + ": " + e.getMessage(),
516 e );
517 }
518 catch ( IOException e )
519 {
520 throw new DocumentRendererException(
521 "Error transforming Document " + document + ": " + e.getMessage(),
522 e );
523 }
524 finally
525 {
526 IOUtil.close( writer );
527 }
528 }
529
530
531
532
533
534
535
536
537
538 private List<File> parseAllFiles( Map<String, SiteModule> filesToProcess, File outputDirectory,
539 DocumentRendererContext context )
540 throws DocumentRendererException, IOException
541 {
542 List<File> iTextFiles = new LinkedList<File>();
543 for ( Map.Entry<String, SiteModule> entry : filesToProcess.entrySet() )
544 {
545 String key = entry.getKey();
546 SiteModule module = entry.getValue();
547 File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key );
548
549 String outputITextName = key.substring( 0, key.lastIndexOf( "." ) + 1 ) + "xml";
550 File outputITextFileTmp = new File( outputDirectory, outputITextName );
551 outputITextFileTmp.deleteOnExit();
552 if ( !outputITextFileTmp.getParentFile().exists() )
553 {
554 outputITextFileTmp.getParentFile().mkdirs();
555 }
556
557 iTextFiles.add( outputITextFileTmp );
558 parse( fullDoc, module, outputITextFileTmp, context );
559 }
560
561 return iTextFiles;
562 }
563
564
565
566
567
568
569
570
571
572 private List<File> parseTOCFiles( File outputDirectory, DocumentModel documentModel, DocumentRendererContext context )
573 throws DocumentRendererException, IOException
574 {
575 List<File> iTextFiles = new LinkedList<File>();
576 for ( Iterator<DocumentTOCItem> it = documentModel.getToc().getItems().iterator(); it.hasNext(); )
577 {
578 DocumentTOCItem tocItem = it.next();
579
580 if ( tocItem.getRef() == null )
581 {
582 getLogger().debug(
583 "No ref defined for the tocItem '" + tocItem.getName()
584 + "' in the document descriptor. IGNORING" );
585 continue;
586 }
587
588 String href = StringUtils.replace( tocItem.getRef(), "\\", "/" );
589 if ( href.lastIndexOf( "." ) != -1 )
590 {
591 href = href.substring( 0, href.lastIndexOf( "." ) );
592 }
593
594 Collection<SiteModule> modules = siteModuleManager.getSiteModules();
595 for ( SiteModule module : modules )
596 {
597 File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() );
598
599 if ( moduleBasedir.exists() )
600 {
601 String doc = href + "." + module.getExtension();
602 File source = new File( moduleBasedir, doc );
603
604
605 if ( !source.exists() )
606 {
607 if ( href.indexOf( "." + module.getExtension() ) != -1 )
608 {
609 doc = href + ".vm";
610 }
611 else
612 {
613 doc = href + "." + module.getExtension() + ".vm";
614 }
615 source = new File( moduleBasedir, doc );
616 }
617
618 if ( source.exists() )
619 {
620 String outputITextName = doc.substring( 0, doc.lastIndexOf( "." ) + 1 ) + "xml";
621 File outputITextFileTmp = new File( outputDirectory, outputITextName );
622 outputITextFileTmp.deleteOnExit();
623 if ( !outputITextFileTmp.getParentFile().exists() )
624 {
625 outputITextFileTmp.getParentFile().mkdirs();
626 }
627
628 iTextFiles.add( outputITextFileTmp );
629 parse( source, module, outputITextFileTmp, context );
630 }
631 }
632 }
633 }
634
635 return iTextFiles;
636 }
637
638
639
640
641
642
643
644 private String getLogoURL( String logo, File parentFile )
645 {
646 if ( logo == null )
647 {
648 return null;
649 }
650
651 try
652 {
653 return new URL( logo ).toString();
654 }
655 catch ( MalformedURLException e )
656 {
657 try
658 {
659 File f = new File( parentFile, logo );
660 if ( !f.exists() )
661 {
662 getLogger().warn( "The logo " + f.getAbsolutePath() + " doesnt exist. IGNORING" );
663 }
664 else
665 {
666 return f.toURI().toURL().toString();
667 }
668 }
669 catch ( MalformedURLException e1 )
670 {
671 getLogger().debug( "Failed to convert to URL: " + logo, e1 );
672 }
673 }
674
675 return null;
676 }
677 }