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