Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relative paths in html #772

Merged
merged 5 commits into from
Dec 28, 2022
Merged
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
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