Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions core/shared/src/main/scala/laika/api/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,27 @@ abstract class Renderer private[laika] (val config: OperationConfig, skipRewrite
.leftMap(InvalidConfig(_))
}

(if (skipRewrite) Right(targetElement) else rewrite).map { elementToRender =>
val renderContext =
new Formatter.Context[Formatter](
renderFunction,
elementToRender,
Nil,
styles,
doc.path,
pathTranslator,
if (config.compactRendering) Indentation.none else Indentation.default,
config.messageFilters.render
)

val formatter = format.formatterFactory(renderContext)

renderFunction(formatter, elementToRender)
(if (skipRewrite) Right(targetElement) else rewrite).flatMap { elementToRender =>
doc.config
.getOpt[String]("laika.renderTarget.absolute.baseUrl")
.leftMap(InvalidConfig(_))
.map { baseUrlOpt =>
val renderContext =
new Formatter.Context[Formatter](
renderFunction,
elementToRender,
Nil,
styles,
doc.path,
pathTranslator,
if (config.compactRendering) Indentation.none else Indentation.default,
config.messageFilters.render,
baseUrlOpt
)

val formatter = format.formatterFactory(renderContext)
renderFunction(formatter, elementToRender)
}
}
}

Expand Down
15 changes: 12 additions & 3 deletions core/shared/src/main/scala/laika/api/format/Formatter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ abstract class Formatter protected {
*/
def pathTranslator: PathTranslator = context.pathTranslator

/** The absolute base URL to use for rendering internal targets as absolute URLs. */
def internalTargetsAbsoluteBaseUrl: Option[String] = context.internalTargetsAbsoluteBaseUrl

/** Indicates whether internal targets should be rendered as absolute URLs. */
def internalTargetsAbsolute: Boolean = internalTargetsAbsoluteBaseUrl.nonEmpty

/** The styles the new renderer should apply to the rendered elements.
*
* Only used for some special render formats like XSL-FO.
Expand Down Expand Up @@ -172,7 +178,8 @@ object Formatter {
val path: Path,
val pathTranslator: PathTranslator,
val indentation: Formatter.Indentation,
val messageFilter: MessageFilter
val messageFilter: MessageFilter,
val internalTargetsAbsoluteBaseUrl: Option[String]
) {

def forChildElement(child: Element): Context[FMT] =
Expand All @@ -184,7 +191,8 @@ object Formatter {
path,
pathTranslator,
indentation,
messageFilter
messageFilter,
internalTargetsAbsoluteBaseUrl
)

def withIndentation(newValue: Formatter.Indentation): Context[FMT] =
Expand All @@ -196,7 +204,8 @@ object Formatter {
path,
pathTranslator,
newValue,
messageFilter
messageFilter,
internalTargetsAbsoluteBaseUrl
)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,27 @@ private[laika] class HTMLRenderer(format: String)
}
}

def renderTarget(target: Target): String = fmt.pathTranslator.translate(target) match {
case ext: ExternalTarget => ext.url
case int: InternalTarget =>
val relPath = int.relativeTo(fmt.path).relativePath
if (relPath.withoutFragment.toString.endsWith("/index.html"))
relPath.withBasename("").withoutSuffix.toString
else
relPath.toString
}
def renderTarget(target: Target): String =
fmt.pathTranslator.translate(target) match {
case ext: ExternalTarget => ext.url
case int: InternalTarget =>
fmt.internalTargetsAbsoluteBaseUrl match {

case Some(base) =>
val abs = int.render(internalTargetsAbsolute = true)
val normalizedBase =
if (base == "/") ""
else "/" + base.stripPrefix("/").stripSuffix("/")
if (normalizedBase.isEmpty) abs else normalizedBase + abs

case None =>
val relPath = int.relativeTo(fmt.path).relativePath
if (relPath.withoutFragment.toString.endsWith("/index.html"))
relPath.withBasename("").withoutSuffix.toString
else
relPath.toString
}
}

def renderSpanContainer(con: SpanContainer): String = {

Expand Down
75 changes: 75 additions & 0 deletions io/src/test/scala/laika/io/TreeRendererSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import laika.api.Renderer
import laika.api.bundle.{ BundleOrigin, ExtensionBundle, PathTranslator }
import laika.api.config.Origin.TreeScope
import laika.api.config.{ Config, ConfigBuilder, Origin }
import laika.api.config.Origin.DocumentScope
import laika.api.errors.{ InvalidDocument, InvalidDocuments }
import laika.api.format.{ Formatter, TagFormatter }
import laika.ast
Expand Down Expand Up @@ -1307,4 +1308,78 @@ class TreeRendererSpec extends CatsEffectSuite
res.assertEquals(expected)
}

test("render internal targets as absolute when per-document baseUrl is configured") {

val cfgRoot =
ConfigBuilder
.withOrigin(Origin(DocumentScope, Root / "404"))
.withValue("laika.renderTarget.absolute.baseUrl", "/")
.build

val doc404Root =
Document(
Root / "404",
RootElement(
p(SpanLink.internal("/doc.html")("to-doc"))
)
).withConfig(cfgRoot)

val cfgProject =
ConfigBuilder
.withOrigin(Origin(DocumentScope, Root / "404-project"))
.withValue("laika.renderTarget.absolute.baseUrl", "/project")
.build

val doc404Project =
Document(
Root / "404-project",
RootElement(
p(
SpanLink.internal("/doc.html")("to-doc"),
Text(" "),
SpanLink.internal("/index.html")("home"),
Text(" "),
SpanLink.internal("/guide/index.html#install")("install")
)
)
).withConfig(cfgProject)

val normalDoc =
Document(
Root / "doc",
RootElement(
p(SpanLink.internal("/other.html")("to-other"))
)
)

val inputTree =
DocumentTree.builder
.addDocument(doc404Root)
.addDocument(doc404Project)
.addDocument(normalDoc)
.buildRoot

val renderer =
Renderer.of(HTML).parallel[IO].build

renderer.use(
_.from(inputTree)
.toMemory
.render
).map { result =>
val rendered404Root =
result.allDocuments.find(_.path == Root / "404.html").get
val rendered404Project =
result.allDocuments.find(_.path == Root / "404-project.html").get
val renderedDoc =
result.allDocuments.find(_.path == Root / "doc.html").get

assert(rendered404Root.content.contains("""href="/doc.html""""))
assert(rendered404Project.content.contains("""href="/project/doc.html""""))
assert(rendered404Project.content.contains("""href="/project/index.html""""))
assert(rendered404Project.content.contains("""href="/project/guide/index.html#install""""))
assert(renderedDoc.content.contains("""href="other.html""""))
}
}

}