diff --git a/jbake-core/src/main/java/org/jbake/app/Crawler.java b/jbake-core/src/main/java/org/jbake/app/Crawler.java index 0c3d8b9d..d8747d84 100644 --- a/jbake-core/src/main/java/org/jbake/app/Crawler.java +++ b/jbake-core/src/main/java/org/jbake/app/Crawler.java @@ -1,6 +1,5 @@ package org.jbake.app; -import com.orientechnologies.orient.core.record.impl.ODocument; import org.apache.commons.configuration2.CompositeConfiguration; import org.apache.commons.io.FilenameUtils; import org.jbake.app.configuration.JBakeConfiguration; @@ -19,7 +18,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Date; -import java.util.Map; /** * Crawls a file system looking for content. @@ -269,9 +267,9 @@ private void processSourceFile(final File sourceFile, final String sha1, final S if (DocumentTypes.contains(document.getType())) { addAdditionalDocumentAttributes(document, sourceFile, sha1, uri); - if (config.getImgPathUpdate()) { - // Prevent image source url's from breaking - HtmlUtil.fixImageSourceUrls(document, config); + if (config.getImgPathUpdate() || config.getRelativePathUpdate()) { + // Prevent image or other tag's source url's from breaking + HtmlUtil.fixUrls(document, config); } db.addDocument(document); @@ -293,8 +291,8 @@ private void addAdditionalDocumentAttributes(DocumentModel document, File source document.setCached(true); if (document.getStatus().equals(ModelAttributes.Status.PUBLISHED_DATE) - && (document.getDate() != null) - && new Date().after(document.getDate())) { + && (document.getDate() != null) + && new Date().after(document.getDate())) { document.setStatus(ModelAttributes.Status.PUBLISHED); } 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 5de7d8e3..8a607f79 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 @@ -12,6 +12,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.jbake.app.configuration.PropertyList.*; @@ -228,7 +229,6 @@ public void setDatabaseStore(String storeType) { } - @Override public String getDateFormat() { return getAsString(DATE_FORMAT.getKey()); @@ -586,7 +586,7 @@ private void setupDefaultDestination() { String destinationPath = getAsString(DESTINATION_FOLDER.getKey()); File destination = new File(destinationPath); - if ( destination.isAbsolute() ) { + if (destination.isAbsolute()) { setDestinationFolder(destination); } else { setDestinationFolder(new File(getSourceFolder(), destinationPath)); @@ -598,7 +598,7 @@ private void setupDefaultAssetFolder() { File asset = new File(assetFolder); - if(asset.isAbsolute()) { + if (asset.isAbsolute()) { setAssetFolder(asset); } else { setAssetFolder(new File(getSourceFolder(), assetFolder)); @@ -609,7 +609,7 @@ private void setupDefaultTemplateFolder() { String templateFolder = getAsString(TEMPLATE_FOLDER.getKey()); File template = new File(templateFolder); - if(template.isAbsolute()) { + if (template.isAbsolute()) { setTemplateFolder(template); } else { setTemplateFolder(new File(getSourceFolder(), templateFolder)); @@ -620,7 +620,7 @@ private void setupDefaultDataFolder() { String dataFolder = getAsString(DATA_FOLDER.getKey()); File data = new File(dataFolder); - if(data.isAbsolute()) { + if (data.isAbsolute()) { setDataFolder(data); } else { setDataFolder(new File(getSourceFolder(), dataFolder)); @@ -645,8 +645,22 @@ public boolean getImgPathPrependHost() { return getAsBoolean(IMG_PATH_PREPEND_HOST.getKey()); } + @Override + public boolean getRelativePathPrependHost() { + return getAsBoolean(RELATIVE_PATH_PREPEND_HOST.getKey()); + } + + public void setRelativePathPrependHost(boolean relativePathPrependHost) { + setBothPathPrependHost(relativePathPrependHost); + } + public void setImgPathPrependHost(boolean imgPathPrependHost) { - setProperty(IMG_PATH_PREPEND_HOST.getKey(), imgPathPrependHost); + setBothPathPrependHost(imgPathPrependHost); + } + + private void setBothPathPrependHost(boolean prependHost) { + setProperty(RELATIVE_PATH_PREPEND_HOST.getKey(), prependHost); + setProperty(IMG_PATH_PREPEND_HOST.getKey(), prependHost); } @Override @@ -654,8 +668,13 @@ public boolean getImgPathUpdate() { return getAsBoolean(IMG_PATH_UPDATE.getKey()); } - public void setImgPathUPdate(boolean imgPathUpdate) { - setProperty(IMG_PATH_UPDATE.getKey(), imgPathUpdate); + public void setImgPathUpdate(boolean imgPathUpdate) { + setBothPathUpdate(imgPathUpdate); + } + + public void setBothPathUpdate(boolean pathUpdate) { + setProperty(IMG_PATH_UPDATE.getKey(), pathUpdate); + setProperty(RELATIVE_PATH_UPDATE.getKey(), pathUpdate); } public List getJbakeProperties() { @@ -693,4 +712,49 @@ public String getAbbreviatedGitHash() { public String getJvmLocale() { return getAsString(JVM_LOCALE.getKey()); } + + @Override + public boolean getRelativePathUpdate() { + return getAsBoolean(RELATIVE_PATH_UPDATE.getKey()); + } + + public void setRelativePathUpdate(boolean relativePathUpdate) { + setBothPathUpdate(relativePathUpdate); + } + + private static final String EQ = "="; + private static final String COMMA = ","; + + @Override + public Map getTagAttributes() { + List pairs = getAsList(RELATIVE_TAG_ATTRIBUTE.getKey()); + Map tagAttribute = new HashMap<>(); + for (String pair : pairs) { + int idx = pair.indexOf(EQ); + if (idx > 0) { + String tag = pair.substring(0, idx); + String attr = pair.substring(idx + 1); + tagAttribute.put(tag, attr); + } + } + return tagAttribute; + } + + public void addTagAttribute(String tag, String attr) { + Map all = new HashMap<>(); + Map map = getTagAttributes(); + if (null != map) { + all.putAll(map); + } + all.put(tag, attr); + String val = all.entrySet().stream() + .map(e -> String.join(EQ, e.getKey(), e.getValue())) + .collect(Collectors.joining(COMMA)); + + setProperty(RELATIVE_TAG_ATTRIBUTE.getKey(), val); + } + + public void setTagAttributes(String... tagAttributes) { + setProperty(RELATIVE_TAG_ATTRIBUTE.getKey(), StringUtils.join(tagAttributes, COMMA)); + } } 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 3a18536b..3fdee879 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 @@ -8,7 +8,7 @@ /** * JBakeConfiguration gives you access to the project configuration. Typically located in a file called jbake.properties. - * + *

* Use one of {@link JBakeConfigurationFactory} methods to create an instance. */ public interface JBakeConfiguration { @@ -325,12 +325,29 @@ public interface JBakeConfiguration { */ boolean getImgPathPrependHost(); + /** + * @return Flag indicating if a relative paths should be prepended with {@link #getSiteHost()} value - only has an effect if + * {@link #getRelativePathUpdate()} is set to true + */ + boolean getRelativePathPrependHost(); + /** * @return Flag indicating if image paths in content should be updated with absolute path (using URI value of content file), * see {@link #getImgPathUpdate()} which allows you to control the absolute path used */ boolean getImgPathUpdate(); + /** + * @return Flag indicating if relative paths in content should be updated with absolute path (using URI value of content file), + * see {@link #getRelativePathUpdate()} which allows you to control the absolute path used + */ + boolean getRelativePathUpdate(); + + /** + * @return Tag and it's attribute name which contains a path that maybe relative. + */ + Map getTagAttributes(); + /** * @return Version of JBake */ diff --git a/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java b/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java index f212c9be..7e41ce8f 100644 --- a/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java +++ b/jbake-core/src/main/java/org/jbake/app/configuration/PropertyList.java @@ -129,6 +129,20 @@ public abstract class PropertyList { "img.path.update", "update image path?" ); + public static final Property RELATIVE_PATH_UPDATE = new Property( + "relative.path.update", + "update relative path?" + ); + + public static final Property RELATIVE_PATH_PREPEND_HOST = new Property( + "relative.path.prepend.host", + "Prepend site.host to relative paths" + ); + + public static final Property RELATIVE_TAG_ATTRIBUTE = new Property( + "relative.tag.attribute", + "Define tag and it's attribute that may contains relative paths" + ); public static final Property IMG_PATH_PREPEND_HOST = new Property( "img.path.prepend.host", 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 3794286d..8e9dbdd7 100644 --- a/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java +++ b/jbake-core/src/main/java/org/jbake/util/HtmlUtil.java @@ -7,34 +7,47 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import java.util.Map; + /** * @author Manik Magar */ public class HtmlUtil { + private static final char SLASH = '/'; + private static final String SLASH_TEXT = String.valueOf(SLASH); + private static final String HTTP = "http://"; + private static final String HTTPS = "https://"; + private static final String WORKING_DIR = "\\./"; + private static final String EMPTY = ""; + private HtmlUtil() { } /** - * Image paths are specified as w.r.t. assets folder. This function prefix site host to all img src except + * Images or file paths are specified as w.r.t. assets folder. This function prefix site host to all path 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 path starts with "./", i.e. relative to the source file, then it first replace that with output file directory and the add site host. * * @param fileContents Map representing file contents * @param configuration Configuration object */ - public static void fixImageSourceUrls(DocumentModel fileContents, JBakeConfiguration configuration) { + public static void fixUrls(DocumentModel fileContents, JBakeConfiguration configuration) { String htmlContent = fileContents.getBody(); - boolean prependSiteHost = configuration.getImgPathPrependHost(); + boolean prependSiteHost = configuration.getImgPathPrependHost() || configuration.getRelativePathPrependHost(); String siteHost = configuration.getSiteHost(); String uri = getDocumentUri(fileContents); Document document = Jsoup.parseBodyFragment(htmlContent); - Elements allImgs = document.getElementsByTag("img"); - - for (Element img : allImgs) { - transformImageSource(img, uri, siteHost, prependSiteHost); + Map pairs = configuration.getTagAttributes(); + for (Map.Entry entry : pairs.entrySet()) { + String tagName = entry.getKey(); + String attKey = entry.getValue(); + Elements allTags = document.getElementsByTag(tagName); + for (Element tag : allTags) { + transformPath(tag, attKey, uri, siteHost, prependSiteHost); + } } //Use body().html() to prevent adding from parsed fragment. @@ -49,46 +62,47 @@ private static String getDocumentUri(DocumentModel fileContents) { uri = removeTrailingSlash(uri); } - if (uri.contains("/")) { + if (uri.contains(SLASH_TEXT)) { uri = removeFilename(uri); } return uri; } - private static void transformImageSource(Element img, String uri, String siteHost, boolean prependSiteHost) { - String source = img.attr("src"); + private static void transformPath(Element tag, String attrKey, String uri, String siteHost, boolean prependSiteHost) { + String path = tag.attr(attrKey); // Now add the root path - if (!source.startsWith("http://") && !source.startsWith("https://")) { - - if (isRelative(source)) { - source = uri + source.replaceFirst("\\./", ""); + if (!isUrl(path)) { + if (isRelative(path)) { + path = uri + path.replaceFirst(WORKING_DIR, EMPTY); } - if (prependSiteHost) { - if (!siteHost.endsWith("/") && isRelative(source)) { - siteHost = siteHost.concat("/"); + if (!siteHost.endsWith(SLASH_TEXT) && isRelative(path)) { + siteHost = siteHost.concat(SLASH_TEXT); } - source = siteHost + source; + path = siteHost + path; } - - img.attr("src", source); + tag.attr(attrKey, path); } } private static String removeFilename(String uri) { - uri = uri.substring(0, uri.lastIndexOf('/') + 1); + uri = uri.substring(0, uri.lastIndexOf(SLASH) + 1); return uri; } private static String removeTrailingSlash(String uri) { - if (uri.endsWith("/")) { + if (uri.endsWith(SLASH_TEXT)) { uri = uri.substring(0, uri.length() - 1); } return uri; } + private static boolean isUrl(String path) { + return path.startsWith(HTTP) || path.startsWith(HTTPS); + } + private static boolean isRelative(String source) { - return !source.startsWith("/"); + return !source.startsWith(SLASH_TEXT); } } diff --git a/jbake-core/src/main/resources/default.properties b/jbake-core/src/main/resources/default.properties index 08c922cd..ec34c22b 100644 --- a/jbake-core/src/main/resources/default.properties +++ b/jbake-core/src/main/resources/default.properties @@ -143,6 +143,12 @@ header.separator=~~~~~~ img.path.update=false # Prepend site.host to image paths img.path.prepend.host=true +# update relative path +relative.path.update=false +# prepend site.host to tag paths +relative.path.prepend.host=true +# relative tag and attribute, for img's src and a's href. +relative.tag.attribute=img=src,a=href # file used to ignore a directory ignore.file=.jbakeignore diff --git a/jbake-core/src/test/java/org/jbake/util/HtmlUtilTest.java b/jbake-core/src/test/java/org/jbake/util/HtmlUtilTest.java index 0d5c4f8c..ba369651 100644 --- a/jbake-core/src/test/java/org/jbake/util/HtmlUtilTest.java +++ b/jbake-core/src/test/java/org/jbake/util/HtmlUtilTest.java @@ -26,7 +26,23 @@ public void shouldNotAddBodyHTMLElement() { fileContent.setUri("blog/2017/05/first_post.html"); fileContent.setBody("

Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).doesNotContain(""); + assertThat(body).doesNotContain(""); + + } + + @Test + public void shouldNotAddBodyHTMLElement0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
Test
"); + + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -43,7 +59,7 @@ public void shouldNotAddSiteHost() { fileContent.setBody("
Test
"); config.setImgPathPrependHost(false); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -51,6 +67,22 @@ public void shouldNotAddSiteHost() { } + @Test + public void shouldNotAddSiteHost0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
"); + config.setRelativePathPrependHost(false); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"blog/2017/05/first.jpg\""); + + } + @Test public void shouldAddSiteHostWithRelativeImageToDocument() { DocumentModel fileContent = new DocumentModel(); @@ -59,13 +91,28 @@ public void shouldAddSiteHostWithRelativeImageToDocument() { fileContent.setBody("
Test
"); config.setImgPathPrependHost(true); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); assertThat(body).contains("src=\"http://www.jbake.org/blog/2017/05/img/deeper/underground.jpg\""); } + @Test + public void shouldAddSiteHostWithRelativeImageToDocument0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
"); + config.setRelativePathPrependHost(true); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/img/deeper/underground.jpg\""); + } + @Test public void shouldAddContentPath() { DocumentModel fileContent = new DocumentModel(); @@ -74,7 +121,7 @@ public void shouldAddContentPath() { fileContent.setBody("
Test
"); config.setImgPathPrependHost(true); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -82,6 +129,21 @@ public void shouldAddContentPath() { } + @Test + public void shouldAddContentPath0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/first.jpg\""); + + } + @Test public void shouldAddContentPathForCurrentDirectory() { DocumentModel fileContent = new DocumentModel(); @@ -90,7 +152,7 @@ public void shouldAddContentPathForCurrentDirectory() { fileContent.setBody("
Test
"); config.setImgPathPrependHost(true); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -98,6 +160,21 @@ public void shouldAddContentPathForCurrentDirectory() { } + @Test + public void shouldAddContentPathForCurrentDirectory0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/first.jpg\""); + + } + @Test public void shouldNotAddRootPath() { DocumentModel fileContent = new DocumentModel(); @@ -105,7 +182,7 @@ public void shouldNotAddRootPath() { fileContent.setUri("blog/2017/05/first_post.html"); fileContent.setBody("
Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -113,6 +190,21 @@ public void shouldNotAddRootPath() { } + @Test + public void shouldNotAddRootPath0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/first.jpg\""); + + } + @Test public void shouldNotAddRootPathForNoExtension() { DocumentModel fileContent = new DocumentModel(); @@ -121,7 +213,7 @@ public void shouldNotAddRootPathForNoExtension() { fileContent.setNoExtensionUri("blog/2017/05/first_post/"); fileContent.setBody("
Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -129,6 +221,22 @@ public void shouldNotAddRootPathForNoExtension() { } + @Test + public void shouldNotAddRootPathForNoExtension0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setNoExtensionUri("blog/2017/05/first_post/"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/first.jpg\""); + + } + @Test public void shouldAddContentPathForNoExtension() { DocumentModel fileContent = new DocumentModel(); @@ -137,13 +245,28 @@ public void shouldAddContentPathForNoExtension() { fileContent.setNoExtensionUri("blog/2017/05/first_post/"); fileContent.setBody("
Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); assertThat(body).contains("src=\"http://www.jbake.org/blog/2017/05/first.jpg\""); } + @Test + public void shouldAddContentPathForNoExtension0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setNoExtensionUri("blog/2017/05/first_post/"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://www.jbake.org/blog/2017/05/first.jpg\""); + } + @Test public void shouldNotChangeForHTTP() { DocumentModel fileContent = new DocumentModel(); @@ -152,7 +275,7 @@ public void shouldNotChangeForHTTP() { fileContent.setNoExtensionUri("blog/2017/05/first_post/"); fileContent.setBody("
Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); @@ -160,6 +283,22 @@ public void shouldNotChangeForHTTP() { } + @Test + public void shouldNotChangeForHTTP0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setNoExtensionUri("blog/2017/05/first_post/"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"http://example.com/first.jpg\""); + + } + @Test public void shouldNotChangeForHTTPS() { DocumentModel fileContent = new DocumentModel(); @@ -168,10 +307,25 @@ public void shouldNotChangeForHTTPS() { fileContent.setNoExtensionUri("blog/2017/05/first_post/"); fileContent.setBody("
Test
"); - HtmlUtil.fixImageSourceUrls(fileContent, config); + HtmlUtil.fixUrls(fileContent, config); String body = fileContent.getBody(); assertThat(body).contains("src=\"https://example.com/first.jpg\""); } + + @Test + public void shouldNotChangeForHTTPS0() { + DocumentModel fileContent = new DocumentModel(); + fileContent.setRootPath("../../../"); + fileContent.setUri("blog/2017/05/first_post.html"); + fileContent.setNoExtensionUri("blog/2017/05/first_post/"); + fileContent.setBody("
"); + + HtmlUtil.fixUrls(fileContent, config); + + String body = fileContent.getBody(); + + assertThat(body).contains("href=\"https://example.com/first.jpg\""); + } }