Skip to content

Commit 746aa43

Browse files
committed
refactor: merge BuildTocDocument/TocDocumentProcessor with base type
1 parent ecd2b7d commit 746aa43

File tree

4 files changed

+231
-287
lines changed

4 files changed

+231
-287
lines changed

src/Docfx.Build/TableOfContents/BuildTocDocument.cs

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44
using System.Collections.Concurrent;
55
using System.Collections.Immutable;
66
using System.Composition;
7-
7+
using System.Web;
8+
using Docfx.Build.Common;
89
using Docfx.Common;
910
using Docfx.DataContracts.Common;
1011
using Docfx.Plugins;
1112

1213
namespace Docfx.Build.TableOfContents;
1314

1415
[Export(nameof(TocDocumentProcessor), typeof(IDocumentBuildStep))]
15-
public class BuildTocDocument : BuildTocDocumentStepBase
16+
public class BuildTocDocument : BaseDocumentBuildStep
1617
{
17-
#region Override methods
18-
1918
public override string Name => nameof(BuildTocDocument);
2019

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

36-
#endregion
35+
public override void Build(FileModel model, IHostService host)
36+
{
37+
var toc = (TocItemViewModel)model.Content;
38+
TocRestructureUtility.Restructure(toc, host.TableOfContentRestructions);
39+
BuildCore(toc, model, host);
40+
// todo : metadata.
41+
}
42+
43+
private static void BuildCore(TocItemViewModel item, FileModel model, IHostService hostService, string includedFrom = null)
44+
{
45+
if (item == null)
46+
{
47+
return;
48+
}
49+
50+
var linkToUids = new HashSet<string>();
51+
var linkToFiles = new HashSet<string>();
52+
var uidLinkSources = new Dictionary<string, ImmutableList<LinkSourceInfo>>();
53+
var fileLinkSources = new Dictionary<string, ImmutableList<LinkSourceInfo>>();
54+
55+
if (Utility.IsSupportedRelativeHref(item.Href))
56+
{
57+
UpdateDependencies(linkToFiles, fileLinkSources, item.Href);
58+
}
59+
if (Utility.IsSupportedRelativeHref(item.Homepage))
60+
{
61+
UpdateDependencies(linkToFiles, fileLinkSources, item.Homepage);
62+
}
63+
if (!string.IsNullOrEmpty(item.TopicUid))
64+
{
65+
UpdateDependencies(linkToUids, uidLinkSources, item.TopicUid);
66+
}
67+
68+
model.LinkToUids = model.LinkToUids.Union(linkToUids);
69+
model.LinkToFiles = model.LinkToFiles.Union(linkToFiles);
70+
model.UidLinkSources = model.UidLinkSources.Merge(uidLinkSources);
71+
model.FileLinkSources = model.FileLinkSources.Merge(fileLinkSources);
72+
73+
includedFrom = item.IncludedFrom ?? includedFrom;
74+
if (item.Items != null)
75+
{
76+
foreach (var i in item.Items)
77+
{
78+
BuildCore(i, model, hostService, includedFrom);
79+
}
80+
}
81+
82+
void UpdateDependencies(HashSet<string> linkTos, Dictionary<string, ImmutableList<LinkSourceInfo>> linkSources, string link)
83+
{
84+
var path = HttpUtility.UrlDecode(UriUtility.GetPath(link));
85+
var anchor = UriUtility.GetFragment(link);
86+
linkTos.Add(path);
87+
AddOrUpdate(linkSources, path, GetLinkSourceInfo(path, anchor, model.File, includedFrom));
88+
}
89+
}
90+
91+
private static string ParseFile(string link)
92+
{
93+
var queryIndex = link.IndexOfAny(new[] { '?', '#' });
94+
return queryIndex == -1 ? link : link.Remove(queryIndex);
95+
}
3796

38-
#region Private methods
97+
private static void AddOrUpdate(Dictionary<string, ImmutableList<LinkSourceInfo>> dict, string path, LinkSourceInfo source)
98+
=> dict[path] = dict.TryGetValue(path, out var sources) ? sources.Add(source) : ImmutableList.Create(source);
99+
100+
private static LinkSourceInfo GetLinkSourceInfo(string path, string anchor, string source, string includedFrom)
101+
{
102+
return new LinkSourceInfo
103+
{
104+
SourceFile = includedFrom ?? source,
105+
Anchor = anchor,
106+
Target = path,
107+
};
108+
}
39109

40110
private static void ReportPreBuildDependency(List<FileModel> models, IHostService host, int parallelism, HashSet<string> includedTocs)
41111
{
@@ -187,6 +257,4 @@ public RelativeInfo(TocInfo tocInfo, FileAndType article)
187257
public RelativeInfo(FileModel tocModel, FileAndType article)
188258
: this(new TocInfo(tocModel), article) { }
189259
}
190-
191-
#endregion
192260
}

src/Docfx.Build/TableOfContents/BuildTocDocumentStepBase.cs

Lines changed: 0 additions & 96 deletions
This file was deleted.

src/Docfx.Build/TableOfContents/TocDocumentProcessor.cs

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Immutable;
45
using System.Composition;
56
using System.Web;
6-
7+
using Docfx.Build.Common;
78
using Docfx.Common;
89
using Docfx.DataContracts.Common;
910
using Docfx.Plugins;
1011

1112
namespace Docfx.Build.TableOfContents;
1213

1314
[Export(typeof(IDocumentProcessor))]
14-
public class TocDocumentProcessor : TocDocumentProcessorBase
15+
public class TocDocumentProcessor : DisposableDocumentProcessor
1516
{
17+
private static readonly char[] QueryStringOrAnchor = new[] { '#', '?' };
18+
1619
public override string Name => nameof(TocDocumentProcessor);
1720

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

31-
protected override void RegisterTocToContext(TocItemViewModel toc, FileModel model, IDocumentBuildContext context)
34+
public override FileModel Load(FileAndType file, ImmutableDictionary<string, object> metadata)
35+
{
36+
var filePath = file.FullPath;
37+
var toc = TocHelper.LoadSingleToc(filePath);
38+
39+
var displayLocalPath = PathUtility.MakeRelativePath(EnvironmentContext.BaseDirectory, file.FullPath);
40+
41+
// Apply metadata to TOC
42+
foreach (var (key, value) in metadata.OrderBy(item => item.Key))
43+
{
44+
toc.Metadata[key] = value;
45+
}
46+
47+
return new FileModel(file, toc)
48+
{
49+
LocalPathFromRoot = displayLocalPath
50+
};
51+
}
52+
53+
public override SaveResult Save(FileModel model)
54+
{
55+
return new SaveResult
56+
{
57+
DocumentType = Constants.DocumentType.Toc,
58+
FileWithoutExtension = Path.ChangeExtension(model.File, null),
59+
LinkToFiles = model.LinkToFiles.ToImmutableArray(),
60+
LinkToUids = model.LinkToUids,
61+
FileLinkSources = model.FileLinkSources,
62+
UidLinkSources = model.UidLinkSources,
63+
};
64+
}
65+
66+
public override void UpdateHref(FileModel model, IDocumentBuildContext context)
67+
{
68+
var toc = (TocItemViewModel)model.Content;
69+
UpdateTocItemHref(toc, model, context);
70+
71+
RegisterTocToContext(toc, model, context);
72+
model.Content = toc;
73+
}
74+
75+
private void UpdateTocItemHref(TocItemViewModel toc, FileModel model, IDocumentBuildContext context, string includedFrom = null)
76+
{
77+
if (toc.IsHrefUpdated) return;
78+
79+
ResolveUid(toc, model, context, includedFrom);
80+
81+
// Have to register TocMap after uid is resolved
82+
RegisterTocMapToContext(toc, model, context);
83+
84+
toc.Homepage = ResolveHref(toc.Homepage, toc.OriginalHomepage, model, context, nameof(toc.Homepage));
85+
toc.OriginalHomepage = null;
86+
toc.Href = ResolveHref(toc.Href, toc.OriginalHref, model, context, nameof(toc.Href));
87+
toc.OriginalHref = null;
88+
toc.TocHref = ResolveHref(toc.TocHref, toc.OriginalTocHref, model, context, nameof(toc.TocHref));
89+
toc.OriginalTocHref = null;
90+
toc.TopicHref = ResolveHref(toc.TopicHref, toc.OriginalTopicHref, model, context, nameof(toc.TopicHref));
91+
toc.OriginalTopicHref = null;
92+
93+
includedFrom = toc.IncludedFrom ?? includedFrom;
94+
if (toc.Items != null && toc.Items.Count > 0)
95+
{
96+
foreach (var item in toc.Items)
97+
{
98+
UpdateTocItemHref(item, model, context, includedFrom);
99+
}
100+
}
101+
102+
toc.IsHrefUpdated = true;
103+
}
104+
105+
private static void ResolveUid(TocItemViewModel item, FileModel model, IDocumentBuildContext context, string includedFrom)
106+
{
107+
if (item.TopicUid != null)
108+
{
109+
var xref = GetXrefFromUid(item.TopicUid, model, context, includedFrom);
110+
if (xref != null)
111+
{
112+
item.Href = item.TopicHref = xref.Href;
113+
if (string.IsNullOrEmpty(item.Name))
114+
{
115+
item.Name = xref.Name;
116+
}
117+
118+
if (string.IsNullOrEmpty(item.NameForCSharp) && xref.TryGetXrefStringValue("name.csharp", out var nameForCSharp))
119+
{
120+
item.NameForCSharp = nameForCSharp;
121+
}
122+
if (string.IsNullOrEmpty(item.NameForVB) && xref.TryGetXrefStringValue("name.vb", out var nameForVB))
123+
{
124+
item.NameForVB = nameForVB;
125+
}
126+
}
127+
}
128+
}
129+
130+
private static XRefSpec GetXrefFromUid(string uid, FileModel model, IDocumentBuildContext context, string includedFrom)
131+
{
132+
var xref = context.GetXrefSpec(uid);
133+
if (xref == null)
134+
{
135+
Logger.LogWarning(
136+
$"Unable to find file with uid \"{uid}\" referenced by TOC file \"{includedFrom ?? model.LocalPathFromRoot}\"",
137+
code: WarningCodes.Build.UidNotFound,
138+
file: includedFrom);
139+
}
140+
return xref;
141+
}
142+
143+
private static string ResolveHref(string pathToFile, string originalPathToFile, FileModel model, IDocumentBuildContext context, string propertyName)
144+
{
145+
if (!Utility.IsSupportedRelativeHref(pathToFile))
146+
{
147+
return pathToFile;
148+
}
149+
150+
var index = pathToFile.IndexOfAny(QueryStringOrAnchor);
151+
if (index == 0)
152+
{
153+
var message = $"Invalid toc link for {propertyName}: {originalPathToFile}.";
154+
Logger.LogError(message, code: ErrorCodes.Toc.InvalidTocLink);
155+
throw new DocumentException(message);
156+
}
157+
158+
var path = UriUtility.GetPath(pathToFile);
159+
var segments = UriUtility.GetQueryStringAndFragment(pathToFile);
160+
161+
var fli = new FileLinkInfo(model.LocalPathFromRoot, model.File, path, context);
162+
var href = context.HrefGenerator?.GenerateHref(fli);
163+
164+
// Check href is modified by HrefGenerator or not.
165+
if (href != null && href != fli.Href)
166+
{
167+
return UriUtility.MergeHref(href, segments);
168+
}
169+
170+
if (fli.ToFileInDest == null)
171+
{
172+
// original path to file can be null for files generated by docfx in PreBuild
173+
var displayFilePath = string.IsNullOrEmpty(originalPathToFile) ? pathToFile : originalPathToFile;
174+
Logger.LogInfo($"Unable to find file \"{displayFilePath}\" for {propertyName} referenced by TOC file \"{model.LocalPathFromRoot}\"");
175+
return originalPathToFile;
176+
}
177+
178+
// fragment and query in original href takes precedence over the one from hrefGenerator
179+
return fli.Href + segments;
180+
}
181+
182+
private void RegisterTocToContext(TocItemViewModel toc, FileModel model, IDocumentBuildContext context)
32183
{
33184
var key = model.Key;
34185

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

52-
protected override void RegisterTocMapToContext(TocItemViewModel item, FileModel model, IDocumentBuildContext context)
203+
private void RegisterTocMapToContext(TocItemViewModel item, FileModel model, IDocumentBuildContext context)
53204
{
54205
var key = model.Key;
55206
// If tocHref is set, href is originally RelativeFolder type, and href is set to the homepage of TocHref,

0 commit comments

Comments
 (0)