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