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