Skip to content

Commit

Permalink
Merge pull request #772 from nojaf/relative-paths-in-html
Browse files Browse the repository at this point in the history
Fixes #769
  • Loading branch information
baronfel authored Dec 28, 2022
2 parents 6a63897 + 2da6dc1 commit d0c2d48
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 1 deletion.
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## 17.2.0

* Resolve markdown links in raw html [#769](https://github.com/fsprojects/FSharp.Formatting/issues/769)

## 17.1.0

* [Add syntax highlighting to API docs](https://github.com/fsprojects/FSharp.Formatting/pull/780)

## 17.0.0
Expand Down
33 changes: 32 additions & 1 deletion src/FSharp.Formatting.Markdown/MarkdownUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace rec FSharp.Formatting.Markdown

open System.Collections.Generic
open System.Linq
open System.Xml.Linq
open FSharp.Formatting.Templating

module internal MarkdownUtils =
Expand Down Expand Up @@ -315,7 +317,36 @@ module internal MarkdownUtils =
)
| OtherBlock (lines: (string * MarkdownRange) list, range) ->
OtherBlock(lines |> List.map (fun (line, range) -> (mapText f line, range)), range)
| InlineHtmlBlock (code, count, range) -> InlineHtmlBlock(mapText f code, count, range)
| InlineHtmlBlock (code, count, range) ->
try
let fText, _, fLink = f

if code.StartsWith("<pre") || code.StartsWith("<table class=\"pre\"") then
// Skip check for non-user html
// Should be even run that code through `fText`?
InlineHtmlBlock(fText code, count, range)
else
let tempRoot = "fsdocs-secret-temp-root"
// We can't be sure code is a single html element, we could get multiple elements.
let element = XElement.Parse($"<{tempRoot}>{code}</{tempRoot}>")
// ends-with is XPath 2.0 only, https://stackoverflow.com/questions/1525299/xpath-and-xslt-2-0-for-net
let attributes =
match System.Xml.XPath.Extensions.XPathEvaluate(element, "//*/@*[contains(., '.md')]") with
| :? System.Collections.IEnumerable as enumerable ->
enumerable |> Enumerable.Cast<XAttribute> |> Seq.toArray
| _ -> Array.empty

if Array.isEmpty attributes then
InlineHtmlBlock(fText code, count, range)
else
for attribute in attributes do
if attribute.Value.EndsWith(".md") then
attribute.SetValue(fLink attribute.Value)

let html = element.Elements() |> Seq.map string |> String.concat "" |> fText
InlineHtmlBlock(html, count, range)
with ex ->
InlineHtmlBlock(mapText f code, count, range)

// NOTE: substitutions are not currently applied to embedded LiterateParagraph which are in any case eliminated
// before substitutions are applied.
Expand Down
58 changes: 58 additions & 0 deletions tests/FSharp.Markdown.Tests/Markdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,61 @@ let ``Do not close emphasis if second * is preceded by punctuation and followed
let doc2 = "*(*foo*)*"
let actual2 = "<p><em>(<em>foo</em>)</em></p>\r\n" |> properNewLines
Markdown.ToHtml doc2 |> shouldEqual actual2

[<Test>]
let ``Replace relative markdown file in anchor href attribute`` () =
let doc = "<a href=\"./other-file.md\" target=\"_blank\">my link</a>"
let mdlinkResolver _ = Some "./other-file.html"

let actual =
"<a href=\"./other-file.html\" target=\"_blank\">my link</a>\r\n"
|> properNewLines

Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

[<Test>]
let ``Replace relative markdown file in multiple anchors`` () =
let doc = "<a href=\"./other-file.md\">link one</a><a href=\"./other-file.md\">link two</a>"
let mdlinkResolver _ = Some "./other-file.html"

let actual =
"<a href=\"./other-file.html\">link one</a><a href=\"./other-file.html\">link two</a>\r\n"
|> properNewLines

Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

[<Test>]
let ``Replace relative markdown file in multiple attributes`` () =
let doc = "<a b=\"./other-file.md\" c=\"./other-file.md\">d</a>"
let mdlinkResolver _ = Some "./other-file.html"
let actual = "<a b=\"./other-file.html\" c=\"./other-file.html\">d</a>\r\n" |> properNewLines
Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

[<Test>]
let ``Replace relative markdown file in custom attribute`` () =
let doc = "<x-web-component data-attribute=\"./other-file.md\"></x-web-component>"
let mdlinkResolver _ = Some "./other-file.html"

let actual =
"<x-web-component data-attribute=\"./other-file.html\"></x-web-component>\r\n"
|> properNewLines

Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

[<Test>]
let ``Don't replace links in generated code block`` () =
let doc = "<pre link=\"valid link though.md\">content</pre>"
let mdlinkResolver _ = failwith "should not be reached!"
let actual = "<pre link=\"valid link though.md\">content</pre>\r\n" |> properNewLines
Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

[<Test>]
let ``Don't replace links in generated code block in table`` () =
let doc = "<table class=\"pre\" link=\"valid link though.md\">content</table>"
let mdlinkResolver _ = failwith "should not be reached!"

let actual =
"<table class=\"pre\" link=\"valid link though.md\">content</table>\r\n"
|> properNewLines

Markdown.ToHtml(doc, mdlinkResolver = mdlinkResolver) |> shouldEqual actual

0 comments on commit d0c2d48

Please sign in to comment.