diff --git a/src/Docfx.Build/PostProcessors/ValidateBookmark.cs b/src/Docfx.Build/PostProcessors/ValidateBookmark.cs index 4415bdf37dc..04199ecbe4a 100644 --- a/src/Docfx.Build/PostProcessors/ValidateBookmark.cs +++ b/src/Docfx.Build/PostProcessors/ValidateBookmark.cs @@ -43,7 +43,7 @@ protected override void HandleCore(HtmlDocument document, ManifestItem manifestI { Title = node.InnerText, Href = TransformPath(outputFile, decodedLink), - Bookmark = bookmark, + Bookmark = Uri.UnescapeDataString(bookmark), SourceFragment = WebUtility.HtmlDecode(node.GetAttributeValue("data-raw-source", null)), SourceFile = WebUtility.HtmlDecode(node.GetAttributeValue("sourceFile", null)), SourceLineNumber = node.GetAttributeValue("sourceStartLineNumber", 0), diff --git a/test/Docfx.Build.Tests/ValidateBookmarkTest.cs b/test/Docfx.Build.Tests/ValidateBookmarkTest.cs index d423ce3424a..d4cb2d3e931 100644 --- a/test/Docfx.Build.Tests/ValidateBookmarkTest.cs +++ b/test/Docfx.Build.Tests/ValidateBookmarkTest.cs @@ -124,4 +124,39 @@ public void TestNoCheck() var actual = logs.Select(l => Tuple.Create(l.Message, l.File)).ToList(); Assert.True(!expected.Except(actual).Any() && expected.Length == actual.Count); } + + [Fact] + public void TestLinkThatContainsNonAsciiChars() + { + Manifest manifest = new() + { + SourceBasePath = _outputFolder, + Files = + { + new ManifestItem { SourceRelativePath = "non_ascii.md", Output = { { ".html", new OutputFileInfo { RelativePath = "non_ascii.html" } } } }, + } + }; + + File.WriteAllText(Path.Combine(_outputFolder, "non_ascii.html"), """ +

foo

+

Visit Québec.

+

Québec

+

The province or the city.

+"""); + + Logger.RegisterListener(_listener); + try + { + new HtmlPostProcessor + { + Handlers = { new ValidateBookmark() } + }.Process(manifest, _outputFolder); + } + finally + { + Logger.UnregisterListener(_listener); + } + var logs = _listener.Items; + Assert.Empty(logs); + } }