/* * Copyright 2012 Joseph Spencer. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spencernetdevelopment; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; import static com.spencernetdevelopment.Logger.*; import java.net.URISyntaxException; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.xml.validation.Schema; import org.w3c.dom.NamedNodeMap; /** * * @author Joseph Spencer */ public class HTMLBuilder { private final FilePath buildDirPath; private final FilePath xmlPagesDirPath; private final Properties variables; private final FileUtils fileUtils; private final String xmlPagesDirString; private final int xmlPagesDirStringLength; private File defaultStylesheet; private final DocumentBuilderFactory docBuilderFactory; private final DocumentBuilder docBuilder; private final TransformerFactory transformerFactory; private final Schema schema; private final StaticPagesConfiguration config; private final HTMLBuilderVisitor transformerVisitor; public HTMLBuilder( FilePath buildDirPath, FilePath pagesDirPath, Properties variables, FileUtils fileUtils, Schema schema, StaticPagesConfiguration config, HTMLBuilderVisitor transformerVisitor ) throws ParserConfigurationException, SAXException { docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); docBuilder = docBuilderFactory.newDocumentBuilder(); transformerFactory = TransformerFactory.newInstance(); this.buildDirPath = buildDirPath; this.xmlPagesDirPath = pagesDirPath; this.variables=variables; this.fileUtils=fileUtils; xmlPagesDirString = xmlPagesDirPath.toString(); xmlPagesDirStringLength = xmlPagesDirString.length(); this.schema=schema; this.config=config; this.transformerVisitor=transformerVisitor; } public void setDefaultStylesheet(File defaultStylesheet) throws IOException, TransformerConfigurationException { assertStylesheetExists(defaultStylesheet); this.defaultStylesheet = defaultStylesheet; } public List<Callable<Object>> getHTMLTasks() throws IOException, SAXException, TransformerException, URISyntaxException, InterruptedException, ExecutionException { if (defaultStylesheet == null) { throw new IllegalStateException("A default stylesheet is required to process xml files."); } List<Path> xmlPagesToBuild = new ArrayList<>(); fileUtils.filePathsToList(xmlPagesDirPath.toFile(), xmlPagesToBuild, ".xml"); List<Callable<Object>> htmlTasks = new ArrayList<>(); for (Path xmlFilePath : xmlPagesToBuild) { htmlTasks.add(new HTMLTask(this, xmlFilePath)); } return htmlTasks; } /** * Builds an xml file within the xmlPagesDir. Converts it to HTML in the * build directory. * * @param xmlFilePath * @throws SAXException * @throws TransformerException * @throws IOException */ public void buildPage(Path xmlFilePath) throws SAXException, TransformerException, IOException { if(isDebug)debug("preparing to build xml file: "+ (xmlFilePath != null ? xmlFilePath.toString() : null)); if ( xmlFilePath != null && !xmlFilePath.toFile().getName().startsWith( config.getPrefixToIgnoreFilesWith() ) ) { try { Document xmlDocument = docBuilder.parse(xmlFilePath.toFile()); xmlDocument.normalize(); schema.newValidator().validate(new DOMSource(xmlDocument)); FilePath outputFilePath = buildDirPath .resolve(xmlFilePath .toString() .substring(xmlPagesDirStringLength + 1) .replaceFirst("\\.xml$", ".html") ); File htmlFile = outputFilePath.toFile(); Node firstChild = xmlDocument.getDocumentElement(); if(isDebug && firstChild == null)debug("firstChild was null"); else if(isDebug)debug("firstChildName: "+firstChild.getLocalName()); String domainRelativePagePath = htmlFile .getAbsolutePath() .substring(buildDirPath.toString().length()); WrappedTransformer transformer = getTransformer( xmlDocument, htmlFile ); Properties vars = new Properties(variables); vars.put("xmlPagePath", xmlFilePath.toString()); vars.put("domainRelativePagePath", domainRelativePagePath); VariableManager variableManager = new VariableManager(vars); transformer.setParameter("VM", variableManager); transformer.setParameter("enableRewrites", true); transformerVisitor.addDefaultParametersTo(transformer); transformer.setParameter("xmlPagePath", xmlFilePath.toString()); transformer.setParameter( "domainRelativePagePath", domainRelativePagePath ); transformer.transform(); } catch(IOException| SAXException| TransformerException ex){ error("An error occurred while attempting to build: "+ xmlFilePath); throw ex; } } } /** * Get a transformer from a path relative to src/xsl. The path gets '.xsl' * appended to it prior to processing. * * @param stylesheet * @return Transformer * @throws TransformerConfigurationException * @throws IOException */ public WrappedTransformer getTransformer( Document xmlDocument, File outputFile ) throws TransformerException, IOException { fileUtils.createFile(outputFile); xmlDocument.normalize(); DOMSource xmlDoc = new DOMSource(xmlDocument); StreamResult resultStream = new StreamResult(outputFile); Node firstChild = xmlDocument.getDocumentElement(); NamedNodeMap attributes = firstChild.getAttributes(); Node stylesheetAttribute = attributes.getNamedItem("stylesheet"); Transformer transformer; if (stylesheetAttribute != null) { String stylesheet = stylesheetAttribute.getNodeValue(); if (stylesheet.trim().length() > 0) { FilePath stylesheetPath = config.getXslDirPath().resolve(stylesheet.concat(".xsl")); File stylesheetFile = stylesheetPath.toFile(); assertStylesheetExists(stylesheetFile); StreamSource stylesheetStream = new StreamSource(stylesheetFile); transformer = transformerFactory.newTransformer(stylesheetStream); } else { throw new IllegalArgumentException( "The stylesheet attribute may not be empty on page elemnts." ); } } else { transformer = transformerFactory.newTransformer(new StreamSource(defaultStylesheet));; } return new WrappedTransformer(transformer, xmlDoc, resultStream); } /** * Asserts that a file exists. * * @param stylesheet * @throws IOException */ private void assertStylesheetExists(File stylesheet) throws IOException { if (stylesheet == null) { throw new NullPointerException("stylesheet was null."); } if(!stylesheet.isFile()) { throw new IOException( "This stylesheet doesn't exist or is not a file:\n " + stylesheet.getAbsolutePath() ); } } }