/* * maven-docbook-plugin - Copyright (C) 2005 OPEN input - http://www.openinput.com/ * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $Id$ */ package org.codehaus.mojo.docbook; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.Fop; import org.apache.fop.apps.FopFactory; import org.apache.fop.apps.MimeConstants; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugin.MojoExecutionException; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; /** * A helper class for transforming DocBook documents into different output formats. * * @author jgonzalez * @author <a href="mailto:lars.trieloff@mindquarry.com">Lars Trieloff</a> */ public class DocumentTransformer { private Set sourcePatterns; protected Log log; protected File sourceDirectory; protected File resourceDirectory; protected File databaseDirectory; protected File outputDirectory; protected URI stylesheetLocation; private boolean generateHtml; private boolean generatePdf; private MojoURIResolver mojoResolver; private String xslfoCustomization; private String xhtmlCustomization; /** * @param log * @param sourceDirectory * @param outputDirectory * @param customizations * @param artifacts */ public DocumentTransformer( Log log, File sourceDirectory, File resourceDirectory, File databaseDirectory, File outputDirectory, URI stylesheetLocation, Map customizations, Collection artifacts ) { this.log = log; this.sourceDirectory = sourceDirectory; this.resourceDirectory = resourceDirectory; this.databaseDirectory = databaseDirectory; this.outputDirectory = outputDirectory; this.stylesheetLocation = stylesheetLocation; this.mojoResolver = new MojoURIResolver( artifacts ); this.xslfoCustomization = (String) customizations.get( "pdf" ); this.xhtmlCustomization = (String) customizations.get( "xhtml" ); this.sourcePatterns = new HashSet( 2 ); this.sourcePatterns.add( "*.xml" ); this.sourcePatterns.add( "**/*xml" ); } public void transform() { this.transform( null ); } /** * @param transformProfile */ public void transform( TransformProfile transformProfile ) { StaleSourceScanner scanner = new StaleSourceScanner( 0, this.sourcePatterns, Collections.EMPTY_SET ); String profile = getFileExtension( transformProfile ); if ( generateHtml ) { scanner.addSourceMapping( new SuffixMapping( ".xml", profile + "html" ) ); } if ( generatePdf ) { scanner.addSourceMapping( new SuffixMapping( ".xml", profile + "pdf" ) ); } Set staleDocbookFiles; try { staleDocbookFiles = scanner.getIncludedSources( this.sourceDirectory, this.outputDirectory ); } catch ( InclusionScanException e ) { throw new RuntimeException( "Error scanning sources in " + sourceDirectory, e ); } if ( staleDocbookFiles.size() > 0 ) { DirectoryScanner docbookScanner = new DirectoryScanner(); docbookScanner.setBasedir( this.sourceDirectory ); docbookScanner.setFollowSymlinks( true ); docbookScanner.setIncludes( new String[] { "**/*.xml", "*.xml" } ); docbookScanner.scan(); String[] docbookFiles = docbookScanner.getIncludedFiles(); this.prepareFileSystem( docbookFiles ); this.transformDocuments( staleDocbookFiles, transformProfile ); } else { this.log.info( "Generated docbook files up to date" ); } if ( this.resourceDirectory.exists() ) { try { DirectoryScanner resourceScanner = new DirectoryScanner(); resourceScanner.setBasedir( this.resourceDirectory); resourceScanner.setExcludes(DirectoryScanner.DEFAULTEXCLUDES); resourceScanner.scan(); String[] includedFiles = resourceScanner.getIncludedFiles(); for ( int i = 0; i < includedFiles.length; i++) { String name = includedFiles[i]; File sourceFile = new File( this.resourceDirectory, name ); File destinationFile = new File( this.outputDirectory, name); if ( !destinationFile.getParentFile().exists() ) { destinationFile.getParentFile().mkdirs(); } FileUtils.copyFile( sourceFile, destinationFile); } } catch ( IOException e ) { throw new RuntimeException( "Unable to copy directory from " + resourceDirectory + " to " + outputDirectory, e ); } } else { this.outputDirectory.mkdirs(); this.log.warn( "Specified resource directory does not exist: " + this.resourceDirectory.toString() ); } } private String getFileExtension( TransformProfile transformProfile ) { StringBuffer profile = new StringBuffer( "." ); if ( transformProfile != null ) { profile.append( transformProfile.getId() ); profile.append( "." ); } return profile.toString(); } /** * @param docbookFiles */ protected void prepareFileSystem( String[] docbookFiles ) { this.log.debug( "Creating output directories for the following files - " + Arrays.asList( docbookFiles ).toString() ); this.outputDirectory.mkdirs(); // TODO: This should be a bit smarter also, shouldn't it? for ( int fileIndex = 0; fileIndex < docbookFiles.length; fileIndex++ ) { String docbookFile = docbookFiles[fileIndex]; int lastFileSeparator = docbookFile.lastIndexOf( File.separator ); if ( lastFileSeparator > 0 ) { File directory = new File( this.outputDirectory, docbookFile.substring( 0, lastFileSeparator ) ); directory.mkdirs(); } } } protected void transformDocuments( Set docbookFiles, TransformProfile transformProfile ) { this.log.info( "Transforming " + docbookFiles.size() + " Docbook stale file(s)" ); Transformer xhtmlTransformer = null; Transformer xslfoTransformer = null; FopFactory fopFactory = null; URI olinkDBURI = new File( this.databaseDirectory + File.separator + "olinkdb.xml" ).toURI(); if ( generateHtml ) { xhtmlTransformer = createXHTMLTransformer( olinkDBURI, transformProfile ); } if ( generatePdf ) { xslfoTransformer = createXSLFOTransformer( olinkDBURI, transformProfile ); fopFactory = FopFactory.newInstance(); fopFactory.setURIResolver( this.mojoResolver ); } Iterator filesIterator = docbookFiles.iterator(); while ( filesIterator.hasNext() ) { File docbookFile = (File) filesIterator.next(); if ( generateHtml ) { transformXhtml( xhtmlTransformer, docbookFile, transformProfile ); } if ( generatePdf ) { transformPdf( xslfoTransformer, docbookFile, fopFactory, transformProfile ); } } } private Transformer createXSLFOTransformer( URI olinkDBURI, TransformProfile transformProfile ) { Transformer xslfoTransformer; TransformerFactory xslfoTf = TransformerFactory.newInstance(); xslfoTf.setURIResolver( this.mojoResolver ); Source docbookStyleSheetSource; String location = this.stylesheetLocation.toASCIIString() + "fo/"; String xslt = null; if ( ( this.xslfoCustomization == null ) && ( transformProfile == null ) ) { xslt = "docbook.xsl"; } else if ( ( this.xslfoCustomization == null ) && ( transformProfile != null ) ) { xslt = "profile-docbook.xsl"; } else { location = this.sourceDirectory.toURI().toString(); xslt = this.xslfoCustomization; } try { docbookStyleSheetSource = this.mojoResolver.resolve( xslt, location ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to resolve " + location + "/" + xslt, e ); } try { xslfoTransformer = xslfoTf.newTransformer( docbookStyleSheetSource ); } catch ( TransformerConfigurationException e ) { throw new RuntimeException( "Unable to create new instance of transformer from source " + docbookStyleSheetSource.getSystemId() ); } xslfoTransformer.setParameter( "target.database.document", olinkDBURI.toString() ); if ( transformProfile != null ) { transformProfile.setParameters( xslfoTransformer ); } this.log.debug( "XSL:FO Style sheet loaded." ); return xslfoTransformer; } private Transformer createXHTMLTransformer( URI olinkDBURI, TransformProfile transformProfile ) { Transformer xhtmlTransformer; TransformerFactory xhtmlTf = TransformerFactory.newInstance(); xhtmlTf.setURIResolver( this.mojoResolver ); Source docbookStyleSheetSource; String location = this.stylesheetLocation.toASCIIString() + "xhtml/"; String xslt = null; if ( ( this.xslfoCustomization == null ) && ( transformProfile == null ) ) { xslt = "docbook.xsl"; } else if ( ( this.xslfoCustomization == null ) && ( transformProfile != null ) ) { xslt = "profile-docbook.xsl"; } else { location = this.sourceDirectory.toURI().toString(); xslt = this.xhtmlCustomization; } try { docbookStyleSheetSource = this.mojoResolver.resolve( xslt, location ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to resolve " + location + "/" + xslt, e ); } try { xhtmlTransformer = xhtmlTf.newTransformer( docbookStyleSheetSource ); } catch ( TransformerConfigurationException e ) { throw new RuntimeException( "Unable to create new instance of transformer from source " + docbookStyleSheetSource.getSystemId() ); } xhtmlTransformer.setParameter( "target.database.document", olinkDBURI.toString() ); xhtmlTransformer.setParameter( "generate.toc", "" ); if ( transformProfile != null ) { transformProfile.setParameters( xhtmlTransformer ); } this.log.debug( "XHTML Style sheet loaded." ); return xhtmlTransformer; } private void transformXhtml( Transformer documentTransformer, File docbookFile, TransformProfile transformProfile ) { this.log.debug( "Processing " + this.sourceDirectory + File.separator + docbookFile ); Source source; try { source = this.mojoResolver.resolve( docbookFile.toURI().toString(), null ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to resolve " + docbookFile.toURI().toString(), e ); } String relativePath = docbookFile.getAbsolutePath().substring( (int) this.sourceDirectory.getAbsolutePath() .length() ); File resultFile = new File( this.outputDirectory, relativePath.substring( 0, relativePath.lastIndexOf( '.' ) ) + getFileExtension( transformProfile ) + "html" ); Result result = new StreamResult( resultFile.getAbsolutePath() ); documentTransformer.setParameter( "current.docid", OLinkDBUpdater.computeFileID( relativePath ) ); // TODO: Parametrize this !!!! documentTransformer.setParameter( "html.stylesheet", this.pathToResources( relativePath ) + "css/xhtml.css" ); try { documentTransformer.transform( source, result ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to transform from source " + source.getSystemId() + " into " + result.getSystemId(), e ); } this.log.debug( "Generated " + this.databaseDirectory + File.separator + docbookFile ); } private void transformPdf( Transformer documentTransformer, File docbookFile, FopFactory fopFactory, TransformProfile transformProfile ) { this.log.debug( "Processing " + this.sourceDirectory + File.separator + docbookFile ); Source source; try { source = this.mojoResolver.resolve( docbookFile.toURI().toString(), null ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to resolve " + docbookFile.toURI().toString(), e ); } String relativePath = docbookFile.getAbsolutePath().substring( (int) this.sourceDirectory.getAbsolutePath() .length() ); File resultFile = new File( this.outputDirectory, relativePath.substring( 0, relativePath.lastIndexOf( '.' ) ) + getFileExtension( transformProfile ) + "pdf" ); this.mojoResolver.setDocumentUri( docbookFile.toURI() ); documentTransformer.setParameter( "current.docid", OLinkDBUpdater.computeFileID( relativePath ) ); OutputStream out; try { out = new BufferedOutputStream( new FileOutputStream( resultFile.getAbsolutePath() ) ); } catch ( FileNotFoundException e ) { throw new RuntimeException( "File not found " + resultFile.getAbsolutePath(), e ); } Result intermediate; try { Fop fop = fopFactory.newFop( MimeConstants.MIME_PDF, out ); intermediate = new SAXResult( fop.getDefaultHandler() ); } catch ( FOPException e ) { throw new RuntimeException( "Unable to create FOP instance", e ); } try { documentTransformer.transform( source, intermediate ); } catch ( TransformerException e ) { throw new RuntimeException( "Unable to transform from source " + source.getSystemId() + " into " + intermediate.getSystemId(), e ); } finally { try { out.close(); } catch ( IOException e ) { // ignore } } this.log.debug( "Generated " + this.databaseDirectory + File.separator + docbookFile ); } protected String pathToResources( String relativePath ) { StringBuffer pathToResources = new StringBuffer(); int separatorIndex = relativePath.indexOf( File.separator, 1 ); while ( separatorIndex != -1 ) { pathToResources.append( "../" ); separatorIndex = relativePath.indexOf( File.separator, separatorIndex + 1 ); } return pathToResources.toString(); } /** * Enables a specified output format. * * @param format the format */ public void enableOutputFormat( String format ) { if ( "xhtml".equalsIgnoreCase( format ) ) { generateHtml = true; } if ( "pdf".equalsIgnoreCase( format ) ) { generatePdf = true; } } }