Skip to content

Commit

Permalink
refactor: merge BuildTocDocument/TocDocumentProcessor with base type
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeih committed Nov 1, 2023
1 parent ecd2b7d commit 746aa43
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 287 deletions.
84 changes: 76 additions & 8 deletions src/Docfx.Build/TableOfContents/BuildTocDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Composition;

using System.Web;
using Docfx.Build.Common;
using Docfx.Common;
using Docfx.DataContracts.Common;
using Docfx.Plugins;

namespace Docfx.Build.TableOfContents;

[Export(nameof(TocDocumentProcessor), typeof(IDocumentBuildStep))]
public class BuildTocDocument : BuildTocDocumentStepBase
public class BuildTocDocument : BaseDocumentBuildStep
{
#region Override methods

public override string Name => nameof(BuildTocDocument);

public override int BuildOrder => 0;
Expand All @@ -33,9 +32,80 @@ public override IEnumerable<FileModel> Prebuild(ImmutableList<FileModel> models,
return resolvedTocModels;
}

#endregion
public override void Build(FileModel model, IHostService host)
{
var toc = (TocItemViewModel)model.Content;
TocRestructureUtility.Restructure(toc, host.TableOfContentRestructions);
BuildCore(toc, model, host);
// todo : metadata.
}

private static void BuildCore(TocItemViewModel item, FileModel model, IHostService hostService, string includedFrom = null)
{
if (item == null)
{
return;
}

var linkToUids = new HashSet<string>();
var linkToFiles = new HashSet<string>();
var uidLinkSources = new Dictionary<string, ImmutableList<LinkSourceInfo>>();
var fileLinkSources = new Dictionary<string, ImmutableList<LinkSourceInfo>>();

if (Utility.IsSupportedRelativeHref(item.Href))
{
UpdateDependencies(linkToFiles, fileLinkSources, item.Href);
}
if (Utility.IsSupportedRelativeHref(item.Homepage))
{
UpdateDependencies(linkToFiles, fileLinkSources, item.Homepage);
}
if (!string.IsNullOrEmpty(item.TopicUid))
{
UpdateDependencies(linkToUids, uidLinkSources, item.TopicUid);
}

model.LinkToUids = model.LinkToUids.Union(linkToUids);
model.LinkToFiles = model.LinkToFiles.Union(linkToFiles);
model.UidLinkSources = model.UidLinkSources.Merge(uidLinkSources);
model.FileLinkSources = model.FileLinkSources.Merge(fileLinkSources);

includedFrom = item.IncludedFrom ?? includedFrom;
if (item.Items != null)
{
foreach (var i in item.Items)
{
BuildCore(i, model, hostService, includedFrom);
}
}

void UpdateDependencies(HashSet<string> linkTos, Dictionary<string, ImmutableList<LinkSourceInfo>> linkSources, string link)
{
var path = HttpUtility.UrlDecode(UriUtility.GetPath(link));
var anchor = UriUtility.GetFragment(link);
linkTos.Add(path);
AddOrUpdate(linkSources, path, GetLinkSourceInfo(path, anchor, model.File, includedFrom));
}
}

private static string ParseFile(string link)
{
var queryIndex = link.IndexOfAny(new[] { '?', '#' });
return queryIndex == -1 ? link : link.Remove(queryIndex);
}

#region Private methods
private static void AddOrUpdate(Dictionary<string, ImmutableList<LinkSourceInfo>> dict, string path, LinkSourceInfo source)
=> dict[path] = dict.TryGetValue(path, out var sources) ? sources.Add(source) : ImmutableList.Create(source);

private static LinkSourceInfo GetLinkSourceInfo(string path, string anchor, string source, string includedFrom)
{
return new LinkSourceInfo
{
SourceFile = includedFrom ?? source,
Anchor = anchor,
Target = path,
};
}

private static void ReportPreBuildDependency(List<FileModel> models, IHostService host, int parallelism, HashSet<string> includedTocs)
{
Expand Down Expand Up @@ -187,6 +257,4 @@ public RelativeInfo(TocInfo tocInfo, FileAndType article)
public RelativeInfo(FileModel tocModel, FileAndType article)
: this(new TocInfo(tocModel), article) { }
}

#endregion
}
96 changes: 0 additions & 96 deletions src/Docfx.Build/TableOfContents/BuildTocDocumentStepBase.cs

This file was deleted.

159 changes: 155 additions & 4 deletions src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Composition;
using System.Web;

using Docfx.Build.Common;
using Docfx.Common;
using Docfx.DataContracts.Common;
using Docfx.Plugins;

namespace Docfx.Build.TableOfContents;

[Export(typeof(IDocumentProcessor))]
public class TocDocumentProcessor : TocDocumentProcessorBase
public class TocDocumentProcessor : DisposableDocumentProcessor
{
private static readonly char[] QueryStringOrAnchor = new[] { '#', '?' };

public override string Name => nameof(TocDocumentProcessor);

[ImportMany(nameof(TocDocumentProcessor))]
Expand All @@ -28,7 +31,155 @@ public override ProcessingPriority GetProcessingPriority(FileAndType file)
return ProcessingPriority.NotSupported;
}

protected override void RegisterTocToContext(TocItemViewModel toc, FileModel model, IDocumentBuildContext context)
public override FileModel Load(FileAndType file, ImmutableDictionary<string, object> metadata)
{
var filePath = file.FullPath;
var toc = TocHelper.LoadSingleToc(filePath);

var displayLocalPath = PathUtility.MakeRelativePath(EnvironmentContext.BaseDirectory, file.FullPath);

// Apply metadata to TOC
foreach (var (key, value) in metadata.OrderBy(item => item.Key))
{
toc.Metadata[key] = value;
}

return new FileModel(file, toc)
{
LocalPathFromRoot = displayLocalPath
};
}

public override SaveResult Save(FileModel model)
{
return new SaveResult
{
DocumentType = Constants.DocumentType.Toc,
FileWithoutExtension = Path.ChangeExtension(model.File, null),
LinkToFiles = model.LinkToFiles.ToImmutableArray(),
LinkToUids = model.LinkToUids,
FileLinkSources = model.FileLinkSources,
UidLinkSources = model.UidLinkSources,
};
}

public override void UpdateHref(FileModel model, IDocumentBuildContext context)
{
var toc = (TocItemViewModel)model.Content;
UpdateTocItemHref(toc, model, context);

RegisterTocToContext(toc, model, context);
model.Content = toc;
}

private void UpdateTocItemHref(TocItemViewModel toc, FileModel model, IDocumentBuildContext context, string includedFrom = null)
{
if (toc.IsHrefUpdated) return;

ResolveUid(toc, model, context, includedFrom);

// Have to register TocMap after uid is resolved
RegisterTocMapToContext(toc, model, context);

toc.Homepage = ResolveHref(toc.Homepage, toc.OriginalHomepage, model, context, nameof(toc.Homepage));
toc.OriginalHomepage = null;
toc.Href = ResolveHref(toc.Href, toc.OriginalHref, model, context, nameof(toc.Href));
toc.OriginalHref = null;
toc.TocHref = ResolveHref(toc.TocHref, toc.OriginalTocHref, model, context, nameof(toc.TocHref));
toc.OriginalTocHref = null;
toc.TopicHref = ResolveHref(toc.TopicHref, toc.OriginalTopicHref, model, context, nameof(toc.TopicHref));
toc.OriginalTopicHref = null;

includedFrom = toc.IncludedFrom ?? includedFrom;
if (toc.Items != null && toc.Items.Count > 0)
{
foreach (var item in toc.Items)
{
UpdateTocItemHref(item, model, context, includedFrom);
}
}

toc.IsHrefUpdated = true;
}

private static void ResolveUid(TocItemViewModel item, FileModel model, IDocumentBuildContext context, string includedFrom)
{
if (item.TopicUid != null)
{
var xref = GetXrefFromUid(item.TopicUid, model, context, includedFrom);
if (xref != null)
{
item.Href = item.TopicHref = xref.Href;
if (string.IsNullOrEmpty(item.Name))
{
item.Name = xref.Name;
}

if (string.IsNullOrEmpty(item.NameForCSharp) && xref.TryGetXrefStringValue("name.csharp", out var nameForCSharp))
{
item.NameForCSharp = nameForCSharp;
}
if (string.IsNullOrEmpty(item.NameForVB) && xref.TryGetXrefStringValue("name.vb", out var nameForVB))
{
item.NameForVB = nameForVB;
}
}
}
}

private static XRefSpec GetXrefFromUid(string uid, FileModel model, IDocumentBuildContext context, string includedFrom)
{
var xref = context.GetXrefSpec(uid);
if (xref == null)
{
Logger.LogWarning(
$"Unable to find file with uid \"{uid}\" referenced by TOC file \"{includedFrom ?? model.LocalPathFromRoot}\"",
code: WarningCodes.Build.UidNotFound,
file: includedFrom);
}
return xref;
}

private static string ResolveHref(string pathToFile, string originalPathToFile, FileModel model, IDocumentBuildContext context, string propertyName)
{
if (!Utility.IsSupportedRelativeHref(pathToFile))
{
return pathToFile;
}

var index = pathToFile.IndexOfAny(QueryStringOrAnchor);
if (index == 0)
{
var message = $"Invalid toc link for {propertyName}: {originalPathToFile}.";
Logger.LogError(message, code: ErrorCodes.Toc.InvalidTocLink);
throw new DocumentException(message);
}

var path = UriUtility.GetPath(pathToFile);
var segments = UriUtility.GetQueryStringAndFragment(pathToFile);

var fli = new FileLinkInfo(model.LocalPathFromRoot, model.File, path, context);
var href = context.HrefGenerator?.GenerateHref(fli);

// Check href is modified by HrefGenerator or not.
if (href != null && href != fli.Href)
{
return UriUtility.MergeHref(href, segments);
}

if (fli.ToFileInDest == null)
{
// original path to file can be null for files generated by docfx in PreBuild
var displayFilePath = string.IsNullOrEmpty(originalPathToFile) ? pathToFile : originalPathToFile;
Logger.LogInfo($"Unable to find file \"{displayFilePath}\" for {propertyName} referenced by TOC file \"{model.LocalPathFromRoot}\"");
return originalPathToFile;
}

// fragment and query in original href takes precedence over the one from hrefGenerator
return fli.Href + segments;
}

private void RegisterTocToContext(TocItemViewModel toc, FileModel model, IDocumentBuildContext context)
{
var key = model.Key;

Expand All @@ -49,7 +200,7 @@ protected override void RegisterTocToContext(TocItemViewModel toc, FileModel mod
context.RegisterTocInfo(tocInfo);
}

protected override void RegisterTocMapToContext(TocItemViewModel item, FileModel model, IDocumentBuildContext context)
private void RegisterTocMapToContext(TocItemViewModel item, FileModel model, IDocumentBuildContext context)
{
var key = model.Key;
// If tocHref is set, href is originally RelativeFolder type, and href is set to the homepage of TocHref,
Expand Down
Loading

0 comments on commit 746aa43

Please sign in to comment.