diff --git a/jbake-core/src/main/java/org/jbake/app/Oven.java b/jbake-core/src/main/java/org/jbake/app/Oven.java index 9eb9a6781..7842545dc 100644 --- a/jbake-core/src/main/java/org/jbake/app/Oven.java +++ b/jbake-core/src/main/java/org/jbake/app/Oven.java @@ -1,5 +1,12 @@ package org.jbake.app; +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.configuration.DefaultJBakeConfiguration; import org.jbake.app.configuration.JBakeConfiguration; @@ -10,16 +17,10 @@ import org.jbake.template.ModelExtractors; import org.jbake.template.ModelExtractorsDocumentTypeListener; import org.jbake.template.RenderingException; +import org.jbake.util.HtmlUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.ServiceLoader; - /** * All the baking happens in the Oven! * @@ -139,11 +140,13 @@ public void bake() { // render content renderContent(); - // copy assets - asset.copy(); - asset.copyAssetsFromContent(config.getContentFolder()); + { + // copy assets + asset.copy(); + asset.copyAssetsFromContent(config.getContentFolder()); - errors.addAll(asset.getErrors()); + errors.addAll(asset.getErrors()); + } LOGGER.info("Baking finished!"); long end = new Date().getTime(); @@ -157,6 +160,40 @@ public void bake() { } } + /** + * Replaces the URLs to resources in documents so that they are pointing to the resources even if the rendered document is placed + * somewhere else than the source markup. + */ + private void makeUrlsAbsolute(ContentStore db, CompositeConfiguration config) + { + int renderedCount = 0; + final List errors = new LinkedList(); + for (String docType : DocumentTypes.getDocumentTypes()) { + DocumentList documentList = db.getUnrenderedContent(docType); + if(documentList == null) continue; + + for (Map documentMap : documentList) { + try { + HtmlUtil.fixImageSourceUrls(documentMap, config); + // Save + documentMap = db.mergeDocument(documentMap).toMap(); + } + catch (Exception e) { + errors.add(e.getMessage()); + } + } + } + + if (!errors.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to render documents. Cause(s):"); + for (String error : errors) { + sb.append("\n ").append(error); + } + throw new RuntimeException(sb.toString()); + } + } + /** * Iterates over the configuration, searching for keys like "template.index.file=..." * in order to register new document types. @@ -186,13 +223,46 @@ private void renderContent() { Renderer renderer = utensils.getRenderer(); ContentStore contentStore = utensils.getContentStore(); - for (RenderingTool tool : ServiceLoader.load(RenderingTool.class)) { + ServiceLoader renderTools = ServiceLoader.load(RenderingTool.class); + + // If this is enabled, then this already happened in Crawler. + // TODO: Remove the fixing from Crawler. + // We should keep the pristine doc body as long as possible, or change it locally. + boolean fixedAlready = config.getMakeImagesUrlAbolute(); + + // 1st pass without altered URLs. + for (RenderingTool tool : renderTools) { + if (!tool.isRendersInPlace() || fixedAlready) + continue; try { renderedCount += tool.render(renderer, contentStore, config); - } catch (RenderingException e) { + } + catch (RenderingException e) { errors.add(e); } } + + // Make the URLs absolute. + if (!fixedAlready) { + makeUrlsAbsolute(contentStore, config); + + // 2nd pass with absolutized URLs. + for (RenderingTool tool : renderTools) { + if (tool.isRendersInPlace()) + continue; + try { + renderedCount += tool.render(renderer, contentStore, config); + } + catch (RenderingException e) { + errors.add(e); + } + } + } + + // mark docs as rendered + for (String docType : DocumentTypes.getDocumentTypes()) { + contentStore.markContentAsRendered(docType); + } } diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java b/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java index 484b14c48..8f6efe24f 100644 --- a/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java +++ b/jbake-core/src/main/java/org/jbake/app/configuration/DefaultJBakeConfiguration.java @@ -526,6 +526,13 @@ public boolean getMakeImagesUrlAbolute() return getAsBoolean(JBakeProperty.IMG_URL_MAKE_ABSOLUTE); } + @Override + public boolean getRelativeImagePathsPointToAssets() + { + return getAsBoolean(JBakeProperty.IMG_PATH_RELATIVE_POINTS_TO_ASSETS); + } + + public void setDestinationFolderName(String folderName) { setProperty(JBakeProperty.DESTINATION_FOLDER, folderName); diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java index fd30f6482..ec637e5fc 100644 --- a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java +++ b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeConfiguration.java @@ -336,6 +336,13 @@ public interface JBakeConfiguration { */ boolean getMakeImagesUrlAbolute(); + /** + * Should JBake prefix with site base URL relative URLs? + * This is not disjunctive from IMAGES_URL_MAKE_ABSOLUTE. + */ + boolean getRelativeImagePathsPointToAssets(); + + /** * Set a property value for the given key * diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeProperty.java b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeProperty.java index 58a5c3d1c..589f53b2e 100644 --- a/jbake-core/src/main/java/org/jbake/app/configuration/JBakeProperty.java +++ b/jbake-core/src/main/java/org/jbake/app/configuration/JBakeProperty.java @@ -46,6 +46,8 @@ public class JBakeProperty { public static final String IMG_PATH_UPDATE = "img.path.update"; public static final String IMG_PATH_PREPEND_HOST = "img.path.prepend.host"; public static final String IMG_URL_MAKE_ABSOLUTE = "img.url.makeAbsolute"; + public static final String IMG_PATH_RELATIVE_POINTS_TO_ASSETS = "img.path.relativePointsToAssets"; + public static final String VERSION = "version"; public static final String EXTRACT_TITLE_FROM_DOC = "extract.title"; diff --git a/jbake-core/src/main/java/org/jbake/render/ArchiveRenderer.java b/jbake-core/src/main/java/org/jbake/render/ArchiveRenderer.java index ba6eebcb3..59c667c2a 100644 --- a/jbake-core/src/main/java/org/jbake/render/ArchiveRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/ArchiveRenderer.java @@ -1,5 +1,6 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; @@ -10,7 +11,7 @@ import java.io.File; -public class ArchiveRenderer implements RenderingTool { +public class ArchiveRenderer extends BaseRenderingTool implements RenderingTool { @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { @@ -33,4 +34,4 @@ public int render(Renderer renderer, ContentStore db, File destination, File tem return render(renderer, db, configuration); } -} \ No newline at end of file +} diff --git a/jbake-core/src/main/java/org/jbake/render/BaseRenderingTool.java b/jbake-core/src/main/java/org/jbake/render/BaseRenderingTool.java new file mode 100644 index 000000000..7cae19ec5 --- /dev/null +++ b/jbake-core/src/main/java/org/jbake/render/BaseRenderingTool.java @@ -0,0 +1,10 @@ +package org.jbake.render; + +public abstract class BaseRenderingTool implements RenderingTool +{ + @Override + public boolean isRendersInPlace() + { + return false; // Most renderers put the result elsewhere. + } +} diff --git a/jbake-core/src/main/java/org/jbake/render/DocumentsRenderer.java b/jbake-core/src/main/java/org/jbake/render/DocumentsRenderer.java index 59fe93e94..40a954243 100644 --- a/jbake-core/src/main/java/org/jbake/render/DocumentsRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/DocumentsRenderer.java @@ -17,6 +17,12 @@ public class DocumentsRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + return render(renderer, db, null); + } + @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { int renderedCount = 0; @@ -71,22 +77,22 @@ public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) } } - /** - * Creates a simple content model to use in individual post navigations. - * - * @param document - * @return - */ - private Map getContentForNav(Map document) { - Map navDocument = new HashMap<>(); - navDocument.put(Attributes.NO_EXTENSION_URI, document.get(Attributes.NO_EXTENSION_URI)); - navDocument.put(Attributes.URI, document.get(Attributes.URI)); - navDocument.put(Attributes.TITLE, document.get(Attributes.TITLE)); - return navDocument; - } - - @Override - public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { - return render(renderer, db, null); - } -} \ No newline at end of file + @Override + public boolean isRendersInPlace() + { + return true; + } + + /** + * Creates a simple content model to use in individual post navigations. + * @param document + * @return + */ + private Map getContentForNav(Map document) { + Map navDocument = new HashMap(); + navDocument.put(Attributes.NO_EXTENSION_URI, document.get(Attributes.NO_EXTENSION_URI)); + navDocument.put(Attributes.URI, document.get(Attributes.URI)); + navDocument.put(Attributes.TITLE, document.get(Attributes.TITLE)); + return navDocument; + } +} diff --git a/jbake-core/src/main/java/org/jbake/render/FeedRenderer.java b/jbake-core/src/main/java/org/jbake/render/FeedRenderer.java index da956df33..7bbe42935 100644 --- a/jbake-core/src/main/java/org/jbake/render/FeedRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/FeedRenderer.java @@ -1,5 +1,6 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; @@ -10,7 +11,7 @@ import java.io.File; -public class FeedRenderer implements RenderingTool { +public class FeedRenderer extends BaseRenderingTool implements RenderingTool { @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { @@ -27,10 +28,11 @@ public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) } } + ///REBASE isn't this in BaseRenderingTool? @Override public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { JBakeConfiguration configuration = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(templatesPath.getParentFile(), config); return render(renderer, db, configuration); } -} \ No newline at end of file +} diff --git a/jbake-core/src/main/java/org/jbake/render/IndexRenderer.java b/jbake-core/src/main/java/org/jbake/render/IndexRenderer.java index bbdb7e4ae..d6aef0c04 100644 --- a/jbake-core/src/main/java/org/jbake/render/IndexRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/IndexRenderer.java @@ -1,5 +1,6 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; @@ -7,9 +8,7 @@ import org.jbake.app.configuration.JBakeConfigurationFactory; import org.jbake.template.RenderingException; -import java.io.File; - -public class IndexRenderer implements RenderingTool { +public class IndexRenderer extends BaseRenderingTool implements RenderingTool { @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { @@ -37,4 +36,4 @@ public int render(Renderer renderer, ContentStore db, File destination, File tem JBakeConfiguration configuration = new JBakeConfigurationFactory().createDefaultJbakeConfiguration(templatesPath.getParentFile(), config); return render(renderer, db, configuration); } -} \ No newline at end of file +} diff --git a/jbake-core/src/main/java/org/jbake/render/RenderingTool.java b/jbake-core/src/main/java/org/jbake/render/RenderingTool.java index 013eaaf2e..b6518dc1f 100644 --- a/jbake-core/src/main/java/org/jbake/render/RenderingTool.java +++ b/jbake-core/src/main/java/org/jbake/render/RenderingTool.java @@ -1,13 +1,12 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; import org.jbake.app.configuration.JBakeConfiguration; import org.jbake.template.RenderingException; -import java.io.File; - public interface RenderingTool { @@ -17,4 +16,9 @@ public interface RenderingTool { //TODO: remove at 3.0.0 int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException; -} \ No newline at end of file + /** + * Does this renderer create a file that will be situated in the same directory as the source markup? + * Serves to keep the URLs intact for renderers that do. + */ + boolean isRendersInPlace(); +} diff --git a/jbake-core/src/main/java/org/jbake/render/SitemapRenderer.java b/jbake-core/src/main/java/org/jbake/render/SitemapRenderer.java index 71bb5756b..3828ee3ac 100644 --- a/jbake-core/src/main/java/org/jbake/render/SitemapRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/SitemapRenderer.java @@ -1,5 +1,6 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; @@ -7,10 +8,8 @@ import org.jbake.app.configuration.JBakeConfigurationFactory; import org.jbake.template.RenderingException; -import java.io.File; - -public class SitemapRenderer implements RenderingTool { +public class SitemapRenderer extends BaseRenderingTool implements RenderingTool { @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { @@ -33,4 +32,4 @@ public int render(Renderer renderer, ContentStore db, File destination, File tem return render(renderer, db, configuration); } -} \ No newline at end of file +} diff --git a/jbake-core/src/main/java/org/jbake/render/TagsRenderer.java b/jbake-core/src/main/java/org/jbake/render/TagsRenderer.java index 2bb1f55df..8b5c7202c 100644 --- a/jbake-core/src/main/java/org/jbake/render/TagsRenderer.java +++ b/jbake-core/src/main/java/org/jbake/render/TagsRenderer.java @@ -1,5 +1,6 @@ package org.jbake.render; +import java.io.File; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ContentStore; import org.jbake.app.Renderer; @@ -7,10 +8,8 @@ import org.jbake.app.configuration.JBakeConfigurationFactory; import org.jbake.template.RenderingException; -import java.io.File; - -public class TagsRenderer implements RenderingTool { +public class TagsRenderer extends BaseRenderingTool implements RenderingTool { @Override public int render(Renderer renderer, ContentStore db, JBakeConfiguration config) throws RenderingException { @@ -32,4 +31,4 @@ public int render(Renderer renderer, ContentStore db, File destination, File tem return render(renderer, db, configuration); } -} \ No newline at end of file +} diff --git a/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java b/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java index 77c200204..0e380e061 100644 --- a/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java +++ b/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java @@ -1,6 +1,9 @@ package org.jbake.util; import java.util.Map; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.lang3.StringUtils; +import org.jbake.app.ConfigUtil.Keys; import org.jbake.app.Crawler.Attributes; import org.jbake.app.configuration.JBakeConfiguration; import org.jsoup.Jsoup; @@ -20,23 +23,30 @@ private HtmlUtil() { * Image paths are specified as w.r.t. assets folder. This function prefix site host to all img src except * the ones that starts with http://, https://. *

- * If image path starts with "./", i.e. relative to the source file, then it first replace that with output file directory and the add site host. + * If the image path is relative to the source file (doesn't start with "/"), + * then it first replace that with output file directory and the add site host. * * @param fileContents Map representing file contents * @param configuration Configuration object + * + * TODO: This is too complicated, will need a refactor again. */ public static void fixImageSourceUrls(Map fileContents, JBakeConfiguration configuration) { - String htmlContent = fileContents.get(Attributes.BODY).toString(); - boolean prependSiteHost = configuration.getImgPathPrependHost(); String siteHost = configuration.getSiteHost(); - String uri = getDocumentUri(fileContents); + siteHost = StringUtils.appendIfMissing(siteHost, "/"); + + boolean prependSiteHost = configuration.getImgPathPrependHost(); + boolean relativePointsToAssets = configuration.getRelativePathPointsToAssets(); + + String dirUri = getDocumentUri(fileContents); + String htmlContent = fileContents.get(Attributes.BODY).toString(); Document document = Jsoup.parseBodyFragment(htmlContent); - Elements allImgs = document.getElementsByTag("img"); + Elements allImgs = document.getElementsByTag("img"); for (Element img : allImgs) { - transformImageSource(img, uri, siteHost, prependSiteHost); + transformImageSource(img, dirUri, siteHost, prependSiteHost, relativePointsToAssets); } // Use body().html() to prevent adding from parsed fragment. @@ -57,21 +67,21 @@ private static String getDocumentUri(Map fileContents) { return dirUri; } - private static void transformImageSource(Element img, String dirUri, String siteHost, boolean prependSiteHost) { + private static void transformImageSource(Element img, String dirUri, String siteHost, + boolean prependSiteHost, + boolean relativePointsToAssets) { String source = img.attr("src"); if (source.startsWith("http://") || source.startsWith("https://")) return; - if (source.startsWith("./")) { + if (isRelativeToSourceMarkup(source, relativePointsToAssets)) + { // Image relative to current content is explicitly specified, lets add dir URI to it. - // The user should have some per-case way to explicitly ask for the dir URI, - // if the config is set not to add URI to relative URLs. - source = source.replaceFirst("^\\./", dirUri); - } - else if (isRelative(source)) { source = dirUri + source; } + //source = StringUtils.removeStart(source, "/"); + if (prependSiteHost) { if (!siteHost.endsWith("/") && isRelative(source)) { siteHost = siteHost.concat("/"); @@ -83,6 +93,11 @@ else if (isRelative(source)) { img.attr("src", source); } + private static boolean isRelativeToSourceMarkup(String source, boolean makeAbsolute) + { + return (makeAbsolute ? source.startsWith("./") : !source.startsWith("/")); + } + private static String removeFilename(String uri) { uri = uri.substring(0, uri.lastIndexOf('/') + 1); return uri; @@ -98,4 +113,5 @@ private static String removeTrailingSlash(String uri) { private static boolean isRelative(String source) { return !source.startsWith("/"); } + } diff --git a/jbake-core/src/main/resources/default.properties b/jbake-core/src/main/resources/default.properties index d55dbe445..249382995 100644 --- a/jbake-core/src/main/resources/default.properties +++ b/jbake-core/src/main/resources/default.properties @@ -118,5 +118,12 @@ header.separator=~~~~~~ img.path.update=false # Prepend site.host to image paths img.path.prepend.host=true -# Should JBake prefix with site base URL? +# Should JBake make URLs full? I.e. http://host/path +# If false, they remain relative, but will be adjusted when rendered outside the original location. +# That may be useful when the rendered HTML is deployed to different hosts or loaded from a filesystem. img.url.makeAbsolute=true + +# Should JBake treat relative (except for "./") as relative to root? +# That means, will be looked up from the site root. +# If false, then they are relative to the file where they are used. +img.url.relativePointsToAssets=true