Skip to content

Commit

Permalink
feat: Add redirection document type (dotnet#8892)
Browse files Browse the repository at this point in the history
  • Loading branch information
filzrev authored and p-kostov committed Jun 28, 2024
1 parent e74f727 commit 932f4c3
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,18 @@ public override SaveResult Save(FileModel model)
throw new NotSupportedException();
}

string documentType = model.DocumentType;
if (string.IsNullOrEmpty(documentType))
{
var properties = (IDictionary<string, object>)model.Content;
documentType = properties.ContainsKey(Constants.PropertyName.RedirectUrl)
? Constants.DocumentType.Redirection
: Constants.DocumentType.Conceptual;
}

var result = new SaveResult
{
DocumentType = model.DocumentType ?? "Conceptual",
DocumentType = documentType,
FileWithoutExtension = Path.ChangeExtension(model.File, null),
LinkToFiles = model.LinkToFiles.ToImmutableArray(),
LinkToUids = model.LinkToUids,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,53 +58,27 @@ public Manifest Process(Manifest manifest, string outputFolder)
return manifest;
}

private IEnumerable<Tuple<string, OutputFileInfo>> GetHtmlOutputFiles(Manifest manifest)
private static IEnumerable<XElement> GetElements(Manifest manifest, Uri baseUri)
{
if (manifest.Files == null)
{
yield break;
}

foreach(var file in manifest.Files)
{
if (file.DocumentType != "Toc"
&& file.OutputFiles.TryGetValue(HtmlExtension, out var info)
&& !string.IsNullOrEmpty(info.RelativePath))
{
yield return Tuple.Create(file.SourceRelativePath, info);
}
}
}
var sitemapOptions = manifest.SitemapOptions;
var sitemapTargetFiles = GetManifestFilesForSitemap(manifest).OrderBy(x => x.relativeHtmlPath);

private IEnumerable<XElement> GetElements(Manifest manifest, Uri baseUri)
{
if (manifest.Files == null)
foreach (var (relativeHtmlPath, file) in sitemapTargetFiles)
{
yield break;
}
var options = GetOptions(sitemapOptions, file.SourceRelativePath);

foreach (var file in (from f in manifest.Files where f.DocumentType != "Toc" orderby f.SourceRelativePath select f))
{
if (file.OutputFiles.TryGetValue(HtmlExtension, out var info) && !string.IsNullOrEmpty(info.RelativePath))
var currentBaseUri = baseUri;
if (options.BaseUrl != sitemapOptions.BaseUrl && !Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out currentBaseUri))
{
var options = GetOptions(manifest.SitemapOptions, file.SourceRelativePath);

var currentBaseUri = baseUri;
if (options.BaseUrl != manifest.SitemapOptions.BaseUrl)
{
if (!Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out currentBaseUri))
{
Logger.LogWarning($"Base url {options.BaseUrl} is not in a valid uri format, use base url from the default setting {manifest.SitemapOptions.BaseUrl} instead.");
currentBaseUri = baseUri;
}
}

yield return GetElement(info.RelativePath, currentBaseUri, options);
Logger.LogWarning($"Base url {options.BaseUrl} is not in a valid uri format, use base url from the default setting {manifest.SitemapOptions.BaseUrl} instead.");
currentBaseUri = baseUri;
}

yield return GetElement(relativeHtmlPath, currentBaseUri, options);
}
}

private XElement GetElement(string relativePath, Uri baseUri, SitemapElementOptions options)
private static XElement GetElement(string relativePath, Uri baseUri, SitemapElementOptions options)
{
var uri = new Uri(baseUri, relativePath);

Expand All @@ -117,7 +91,7 @@ private XElement GetElement(string relativePath, Uri baseUri, SitemapElementOpti
);
}

private SitemapElementOptions GetOptions(SitemapOptions rootOptions, string sourcePath)
private static SitemapElementOptions GetOptions(SitemapOptions rootOptions, string sourcePath)
{
var options = GetMatchingOptions(rootOptions, sourcePath);
if (options == rootOptions)
Expand Down Expand Up @@ -151,7 +125,7 @@ private SitemapElementOptions GetOptions(SitemapOptions rootOptions, string sour
return options;
}

private SitemapElementOptions GetMatchingOptions(SitemapOptions options, string sourcePath)
private static SitemapElementOptions GetMatchingOptions(SitemapOptions options, string sourcePath)
{
if (options.FileOptions != null)
{
Expand All @@ -169,4 +143,40 @@ private SitemapElementOptions GetMatchingOptions(SitemapOptions options, string

return options;
}

private static IEnumerable<(string relativeHtmlPath, ManifestItem manifestItem)> GetManifestFilesForSitemap(Manifest manifest)
{
if (manifest.Files == null)
{
yield break;
}

foreach (var file in manifest.Files)
{
switch (file.DocumentType)
{
// Skip non sitemap target files.
case DataContracts.Common.Constants.DocumentType.Toc:
case DataContracts.Common.Constants.DocumentType.Redirection:
continue;

default:
break;
}

// Skip if manifest don't contains HTML output file.
if (!file.OutputFiles.TryGetValue(HtmlExtension, out var info))
{
continue;
}

// Skip if output HTML relative path is empty.
if (string.IsNullOrEmpty(info.RelativePath))
{
continue;
}

yield return (info.RelativePath, file);
}
}
}
6 changes: 6 additions & 0 deletions src/Microsoft.DocAsCode.DataContracts.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ public static class Constants

public static class DocumentType
{
public const string Conceptual = "Conceptual";
public const string Toc = "Toc";
public const string ManagedReference = "ManagedReference";
public const string Resource = "Resource";
public const string Redirection = "Redirection";
}

public static class PropertyName
Expand Down Expand Up @@ -60,6 +64,8 @@ public static class PropertyName
public const string SystemKeys = "_systemKeys";

public const string OutputFileName = "outputFileName";

public const string RedirectUrl = "redirect_url";
}

public static class MetadataName
Expand Down
8 changes: 8 additions & 0 deletions templates/default/Redirection.html.primary.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
<!DOCTYPE html>
<html {{#_lang}}lang="{{_lang}}"{{/_lang}}>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0;URL='{{redirect_url}}'">
</head>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,48 @@ public void TitleOverwriteH1InMetadataCannotOverwriteTitleFromYamlHeader()
Assert.Equal("This is title from YAML header", title);
}

[Fact]
public void ProcessMarkdownFileWithRedirectUrl()
{
// arrange
const string FileName = "redirection.md";
const string RedirectUrl = "https://example.com";

var metadata = new Dictionary<string, object> { };
var content = $"""
---
redirect_url: {RedirectUrl}
---

# Heading1
Some content
""";
var file = _fileCreator.CreateFile(content, FileName);
var files = new FileCollection(_defaultFiles);
files.Add(DocumentType.Article, new[] { file });

// Add template for redirection.
var templateCreator = new FileCreator(_templateFolder);
templateCreator.CreateFile(@"{{{redirect_url}}}", "redirection.html.tmpl", "default");

// act
BuildDocument(files, metadata);

// assert

// Test `redirection.raw.json` content.
var outputRawModelPath = GetRawModelFilePath(file);
Assert.True(File.Exists(outputRawModelPath));
var model = JsonUtility.Deserialize<Dictionary<string, object>>(outputRawModelPath);
Assert.True(model.TryGetValue(Constants.PropertyName.RedirectUrl, out var redirectUrl));
Assert.Equal(RedirectUrl, redirectUrl);

// Test `manifest.json` content
var manifest = GetOutputManifest();
Assert.True(manifest.Files.Count == 1);
Assert.True(manifest.Files[0].DocumentType == Constants.DocumentType.Redirection);
}

#region Private Helpers
private string GetRawModelFilePath(string fileName)
{
Expand All @@ -361,6 +403,12 @@ private string GetOutputFilePath(string fileName)
return Path.GetFullPath(Path.Combine(_outputFolder, Path.ChangeExtension(fileName, "html")));
}

private Manifest GetOutputManifest()
{
var manifestPath = Path.GetFullPath(Path.Combine(_outputFolder, "manifest.json"));
return JsonUtility.Deserialize<Manifest>(manifestPath);
}

private void BuildDocument(FileCollection files, Dictionary<string, object> metadata = null)
{
var parameters = new DocumentBuildParameters
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DocAsCode.Plugins;
using Microsoft.DocAsCode.Tests.Common;
using Xunit;
using Xunit.Abstractions;

using System.Xml.Linq;

using DocumentType = Microsoft.DocAsCode.DataContracts.Common.Constants.DocumentType;

namespace Microsoft.DocAsCode.Build.Engine.Tests;

[Collection("docfx STA")]
public class SitemapGeneratorTests : TestBase
{
private readonly ITestOutputHelper _output;

public SitemapGeneratorTests(ITestOutputHelper output)
: base()
{
_output = output;
}

public override void Dispose()
{
base.Dispose();
}

[Fact]
public void TestSitemapGenerator()
{
// Arrange
var sitemapGenerator = new SitemapGenerator();
var manifest = new Manifest(new[]
{
// Included items
GetManifestItem(DocumentType.Conceptual),
GetManifestItem(DocumentType.ManagedReference),
GetManifestItem(DocumentType.Resource),
GetManifestItem("Dashboard"),

// Skipped items
GetManifestItem(DocumentType.Toc),
GetManifestItem(DocumentType.Redirection),
GetManifestItem(DocumentType.Conceptual, outputFileExtension: ".txt"), // No HTML output file
}
)
{
SitemapOptions = new SitemapOptions
{
BaseUrl = "https://example.com",
Priority = 1,
ChangeFrequency = PageChangeFrequency.Daily,
LastModified = DateTime.UtcNow
}
};

var outputFolder = GetRandomFolder();
var sitemapPath = Path.Combine(outputFolder, "sitemap.xml");

// Act
manifest = sitemapGenerator.Process(manifest, outputFolder);

// Assert
Assert.Equal("https://example.com/", manifest.SitemapOptions.BaseUrl);
Assert.True(File.Exists(sitemapPath));

var sitemap = XDocument.Load(sitemapPath);
var ns = sitemap.Root.Name.Namespace;
var urls = sitemap.Root.Elements(ns + "url").ToArray();
Assert.True(urls.Length == 4);

// URLs are ordered based on HTML output's RelativePath.
Assert.EndsWith("/Conceptual.html", urls[0].Element(ns + "loc").Value);
Assert.EndsWith("/Dashboard.html", urls[1].Element(ns + "loc").Value);
Assert.EndsWith("/ManagedReference.html", urls[2].Element(ns + "loc").Value);
Assert.EndsWith("/Resource.html", urls[3].Element(ns + "loc").Value);
}

private static ManifestItem GetManifestItem(string documentType, string outputFileExtension = ".html")
{
var result = new ManifestItem
{
DocumentType = documentType,
SourceRelativePath = documentType + ".dummy"
};

if (outputFileExtension != null)
{
result.OutputFiles.Add(outputFileExtension, new OutputFileInfo
{
RelativePath = documentType + outputFileExtension,
});
}

return result;
}
}
Loading

0 comments on commit 932f4c3

Please sign in to comment.