private TocItemInfo ResolveItem(TocItemInfo wrapper, Stack<FileAndType> stack) { using (new LoggerFileScope(wrapper.File.File)) { return ResolveItemCore(wrapper, stack); } }
private TocItemInfo ResolveItem(TocItemInfo wrapper, Stack <FileAndType> stack, bool isRoot = true) { using (new LoggerFileScope(wrapper.File.File)) { return(ResolveItemCore(wrapper, stack, isRoot)); } }
private TocItemViewModel GetReferencedToc(FileAndType tocFile, Stack <FileAndType> stack) { if (_collection.TryGetValue(tocFile.FullPath, out TocItemInfo referencedTocFileModel) || _notInProjectTocCache.TryGetValue(tocFile, out referencedTocFileModel)) { referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedTocFileModel.IsReferenceToc = true; return(referencedTocFileModel.Content); } else { // It is acceptable that the referenced toc file is not included in docfx.json, as long as it can be found locally TocItemViewModel referencedTocItemViewModel; try { referencedTocItemViewModel = TocHelper.LoadSingleToc(tocFile.FullPath); } catch (FileNotFoundException) { Logger.LogError($"Referenced TOC file {tocFile.FullPath} does not exist.", code: WarningCodes.Build.InvalidTocInclude); return(null); } referencedTocFileModel = new TocItemInfo(tocFile, referencedTocItemViewModel); referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); _notInProjectTocCache[tocFile] = referencedTocFileModel; return(referencedTocFileModel.Content); } }
private TocItemViewModel GetReferencedToc(FileAndType tocFile, Stack <FileAndType> stack) { if (_collection.TryGetValue(tocFile.FullPath, out TocItemInfo referencedTocFileModel) || _notInProjectTocCache.TryGetValue(tocFile, out referencedTocFileModel)) { referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedTocFileModel.IsReferenceToc = true; return(referencedTocFileModel.Content); } else { // It is acceptable that the referenced toc file is not included in docfx.json, as long as it can be found locally referencedTocFileModel = new TocItemInfo(tocFile, TocHelper.LoadSingleToc(tocFile.FullPath)); referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); _notInProjectTocCache[tocFile] = referencedTocFileModel; return(referencedTocFileModel.Content); } }
private TocItemInfo ResolveItemCore(TocItemInfo wrapper, Stack <FileAndType> stack, bool isRoot) { if (wrapper.IsResolved) { return(wrapper); } var file = wrapper.File; if (stack.Contains(file)) { throw new DocumentException($"Circular reference to {file.FullPath} is found in {stack.Peek().FullPath}"); } var item = wrapper.Content; // HomepageUid and Uid is deprecated, unified to TopicUid if (string.IsNullOrEmpty(item.TopicUid)) { if (!string.IsNullOrEmpty(item.Uid)) { item.TopicUid = item.Uid; item.Uid = null; } else if (!string.IsNullOrEmpty(item.HomepageUid)) { item.TopicUid = item.HomepageUid; Logger.LogWarning($"HomepageUid is deprecated in TOC. Please use topicUid to specify uid {item.Homepage}"); item.HomepageUid = null; } } // Homepage is deprecated, unified to TopicHref if (!string.IsNullOrEmpty(item.Homepage)) { if (string.IsNullOrEmpty(item.TopicHref)) { item.TopicHref = item.Homepage; } else { Logger.LogWarning($"Homepage is deprecated in TOC. Homepage {item.Homepage} is overwritten with topicHref {item.TopicHref}"); } } // validate href ValidateHref(item); // validate if name is missing if (!isRoot && string.IsNullOrEmpty(item.Name) && string.IsNullOrEmpty(item.TopicUid)) { Logger.LogWarning( $"TOC item ({item.ToString()}) with empty name found. Missing a name?", code: WarningCodes.Build.EmptyTocItemName); } // TocHref supports 2 forms: absolute path and local toc file. // When TocHref is set, using TocHref as Href in output, and using Href as Homepage in output var tocHrefType = Utility.GetHrefType(item.TocHref); // check whether toc exists TocItemInfo tocFileModel = null; if (!string.IsNullOrEmpty(item.TocHref) && (tocHrefType == HrefType.MarkdownTocFile || tocHrefType == HrefType.YamlTocFile)) { var tocFilePath = (RelativePath)file.File + (RelativePath)item.TocHref; var tocFile = file.ChangeFile(tocFilePath); if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { var message = $"Unable to find {item.TocHref}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); } } if (!string.IsNullOrEmpty(item.TocHref)) { if (!string.IsNullOrEmpty(item.Homepage)) { throw new DocumentException( $"TopicHref should be used to specify the homepage for {item.TocHref} when tocHref is used."); } if (tocHrefType == HrefType.RelativeFile || tocHrefType == HrefType.RelativeFolder) { throw new DocumentException($"TocHref {item.TocHref} only supports absolute path or local toc file."); } } var hrefType = Utility.GetHrefType(item.Href); switch (hrefType) { case HrefType.AbsolutePath: case HrefType.RelativeFile: if (item.Items != null && item.Items.Count > 0) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack, false).Content; } if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { var defaultItem = GetDefaultHomepageItem(item); if (defaultItem != null) { item.AggregatedHref = defaultItem.TopicHref; item.AggregatedUid = defaultItem.TopicUid; } } } if (string.IsNullOrEmpty(item.TopicHref)) { // Get homepage from TocHref if href/topicHref is null or empty if (string.IsNullOrEmpty(item.Href) && string.IsNullOrEmpty(item.TopicUid) && tocFileModel != null) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } // Use TopicHref in output model item.TopicHref = item.Href; } break; case HrefType.RelativeFolder: { if (tocFileModel != null) { Logger.LogWarning($"Href {item.Href} is overwritten by tocHref {item.TocHref}"); } else { var relativeFolder = (RelativePath)file.File + (RelativePath)item.Href; var tocFilePath = relativeFolder + (RelativePath)Constants.TableOfContents.YamlTocFileName; var tocFile = file.ChangeFile(tocFilePath); // First, try finding toc.yml under the relative folder // Second, try finding toc.md under the relative folder if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { tocFilePath = relativeFolder + (RelativePath)Constants.TableOfContents.MarkdownTocFileName; tocFile = file.ChangeFile(tocFilePath); if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { var message = $"Unable to find either {Constants.TableOfContents.YamlTocFileName} or {Constants.TableOfContents.MarkdownTocFileName} inside {item.Href}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); break; } } item.TocHref = tocFilePath - (RelativePath)file.File; } // Get homepage from TocHref if TopicHref/TopicUid is not specified if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = item.TopicHref = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } else { item.Href = item.TopicHref; } if (item.Items != null) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack, false).Content; } } } break; case HrefType.MarkdownTocFile: case HrefType.YamlTocFile: { var href = (RelativePath)item.Href; var tocFilePath = (RelativePath)file.File + href; var tocFile = file.ChangeFile(tocFilePath); stack.Push(file); var referencedToc = GetReferencedToc(tocFile, stack); stack.Pop(); // For referenced toc, content from referenced toc is expanded as the items of current toc item, // Href is reset to the homepage of current toc item item.Href = item.TopicHref; var referencedTocClone = referencedToc.Items?.Clone(); // For [reference](a/toc.md), and toc.md contains not-exist.md, the included not-exist.md should be resolved to a/not-exist.md item.Items = UpdateOriginalHref(referencedTocClone, href); } break; default: break; } var relativeToFile = (RelativePath)file.File; item.OriginalHref = item.Href; item.OriginalTocHref = item.TocHref; item.OriginalTopicHref = item.TopicHref; item.OriginalHomepage = item.Homepage; item.Href = NormalizeHref(item.Href, relativeToFile); item.TocHref = NormalizeHref(item.TocHref, relativeToFile); item.TopicHref = NormalizeHref(item.TopicHref, relativeToFile); item.Homepage = NormalizeHref(item.Homepage, relativeToFile); wrapper.IsResolved = true; // for backward compatibility if (item.Href == null && item.Homepage == null) { item.Href = item.TocHref; item.Homepage = item.TopicHref; } return(wrapper); }
private TocItemInfo ResolveItemCore(TocItemInfo wrapper, Stack<FileAndType> stack) { if (wrapper.IsResolved) { return wrapper; } var file = wrapper.File; if (stack.Contains(file)) { throw new DocumentException($"Circular reference to {file.FullPath} is found in {stack.Peek().FullPath}"); } var item = wrapper.Content; // HomepageUid and Uid is deprecated, unified to TopicUid if (string.IsNullOrEmpty(item.TopicUid)) { if (!string.IsNullOrEmpty(item.Uid)) { item.TopicUid = item.Uid; item.Uid = null; } else if (!string.IsNullOrEmpty(item.HomepageUid)) { item.TopicUid = item.HomepageUid; Logger.LogWarning($"HomepageUid is deprecated in TOC. Please use topicUid to specify uid {item.Homepage}"); item.HomepageUid = null; } } // Homepage is deprecated, unified to TopicHref if (!string.IsNullOrEmpty(item.Homepage)) { if (string.IsNullOrEmpty(item.TopicHref)) { item.TopicHref = item.Homepage; } else { Logger.LogWarning($"Homepage is deprecated in TOC. Homepage {item.Homepage} is overwritten with topicHref {item.TopicHref}"); } } // TocHref supports 2 forms: absolute path and local toc file. // When TocHref is set, using TocHref as Href in output, and using Href as Homepage in output var tocHrefType = Utility.GetHrefType(item.TocHref); // check whether toc exists TocItemInfo tocFileModel = null; if (!string.IsNullOrEmpty(item.TocHref) && (tocHrefType == HrefType.MarkdownTocFile || tocHrefType == HrefType.YamlTocFile)) { var tocFilePath = (TypeForwardedToRelativePath)file.File + (TypeForwardedToRelativePath)item.TocHref; var tocFile = file.ChangeFile(tocFilePath); if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { var message = $"Unable to find {item.TocHref}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); } } if (!string.IsNullOrEmpty(item.TocHref)) { if (!string.IsNullOrEmpty(item.Homepage)) { throw new DocumentException( $"TopicHref should be used to specify the homepage for {item.TocHref} when tocHref is used."); } if (tocHrefType == HrefType.RelativeFile || tocHrefType == HrefType.RelativeFolder) { throw new DocumentException($"TocHref {item.TocHref} only supports absolute path or local toc file."); } } var hrefType = Utility.GetHrefType(item.Href); switch (hrefType) { case HrefType.AbsolutePath: case HrefType.RelativeFile: if (item.Items != null && item.Items.Count > 0) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { var defaultItem = GetDefaultHomepageItem(item); if (defaultItem != null) { item.AggregatedHref = defaultItem.TopicHref; item.AggregatedUid = defaultItem.TopicUid; } } } if (string.IsNullOrEmpty(item.TopicHref)) { // Get homepage from TocHref if href/topicHref is null or empty if (string.IsNullOrEmpty(item.Href) && string.IsNullOrEmpty(item.TopicUid) && tocFileModel != null) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } // Use TopicHref in output model item.TopicHref = item.Href; } break; case HrefType.RelativeFolder: { if (tocFileModel != null) { Logger.LogWarning($"Href {item.Href} is overwritten by tocHref {item.TocHref}"); } else { var relativeFolder = (TypeForwardedToRelativePath)file.File + (TypeForwardedToRelativePath)item.Href; var tocFilePath = relativeFolder + (TypeForwardedToRelativePath)Constants.YamlTocFileName; var tocFile = file.ChangeFile(tocFilePath); // First, try finding toc.yml under the relative folder // Second, try finding toc.md under the relative folder if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { tocFilePath = relativeFolder + (TypeForwardedToRelativePath)Constants.MarkdownTocFileName; tocFile = file.ChangeFile(tocFilePath); if (!_collection.TryGetValue(tocFile.FullPath, out tocFileModel)) { var message = $"Unable to find either {Constants.YamlTocFileName} or {Constants.MarkdownTocFileName} inside {item.Href}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); break; } } item.TocHref = tocFilePath - (TypeForwardedToRelativePath)file.File; } // Get homepage from TocHref if TopicHref/TopicUid is not specified if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = item.TopicHref = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } else { item.Href = item.TopicHref; } if (item.Items != null) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } } } break; case HrefType.MarkdownTocFile: case HrefType.YamlTocFile: { var tocFilePath = (TypeForwardedToRelativePath)file.File + (TypeForwardedToRelativePath)item.Href; var tocFile = file.ChangeFile(tocFilePath); TocItemInfo referencedTocFileModel; TocItemViewModel referencedToc; stack.Push(file); if (_collection.TryGetValue(tocFile.FullPath, out referencedTocFileModel) || _notInProjectTocCache.TryGetValue(tocFile, out referencedTocFileModel)) { referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedTocFileModel.IsReferenceToc = true; referencedToc = referencedTocFileModel.Content; } else { // It is acceptable that the referenced toc file is not included in docfx.json, as long as it can be found locally referencedTocFileModel = new TocItemInfo(tocFile, new TocItemViewModel { Items = Utility.LoadSingleToc(tocFile.FullPath) }); referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedToc = referencedTocFileModel.Content; _notInProjectTocCache[tocFile] = referencedTocFileModel; } stack.Pop(); // For referenced toc, content from referenced toc is expanded as the items of current toc item, // Href is reset to the homepage of current toc item item.Href = item.TopicHref; item.Items = referencedToc.Items; } break; default: break; } var relativeToFile = (TypeForwardedToRelativePath)file.File; item.OriginalHref = item.Href; item.OriginalTocHref = item.TocHref; item.OriginalTopicHref = item.TopicHref; item.OriginalHomepage = item.Homepage; item.Href = NormalizeHref(item.Href, relativeToFile); item.TocHref = NormalizeHref(item.TocHref, relativeToFile); item.TopicHref = NormalizeHref(item.TopicHref, relativeToFile); item.Homepage = NormalizeHref(item.Homepage, relativeToFile); wrapper.IsResolved = true; // for backward compatibility if (item.Href == null && item.Homepage == null) { item.Href = item.TocHref; item.Homepage = item.TopicHref; } return wrapper; }
private TocItemInfo ResolveItemCore(TocItemInfo wrapper, Stack <FileAndType> stack) { if (wrapper.IsResolved) { return(wrapper); } var file = wrapper.File; if (stack.Contains(file)) { throw new DocumentException($"Circular reference to {file.FullPath} is found in {stack.Peek().FullPath}"); } var item = wrapper.Content; // HomepageUid and Uid is deprecated, unified to TopicUid if (string.IsNullOrEmpty(item.TopicUid)) { if (!string.IsNullOrEmpty(item.Uid)) { item.TopicUid = item.Uid; item.Uid = null; } else if (!string.IsNullOrEmpty(item.HomepageUid)) { item.TopicUid = item.HomepageUid; Logger.LogWarning($"HomepageUid is deprecated in TOC. Please use topicUid to specify uid {item.Homepage}"); item.HomepageUid = null; } } // Homepage is deprecated, unified to TopicHref if (!string.IsNullOrEmpty(item.Homepage)) { if (string.IsNullOrEmpty(item.TopicHref)) { item.TopicHref = item.Homepage; } else { Logger.LogWarning($"Homepage is deprecated in TOC. Homepage {item.Homepage} is overwritten with topicHref {item.TopicHref}"); } } // TocHref supports 2 forms: absolute path and local toc file. // When TocHref is set, using TocHref as Href in output, and using Href as Homepage in output var tocHrefType = Utility.GetHrefType(item.TocHref); // check whether toc exists TocItemInfo tocFileModel = null; if (!string.IsNullOrEmpty(item.TocHref) && (tocHrefType == HrefType.MarkdownTocFile || tocHrefType == HrefType.YamlTocFile)) { var tocFilePath = (RelativePath)file.File + (RelativePath)item.TocHref; var tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); if (!_collection.TryGetValue(tocFile, out tocFileModel)) { var message = $"Unable to find {item.TocHref}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); } } if (!string.IsNullOrEmpty(item.TocHref)) { if (!string.IsNullOrEmpty(item.Homepage)) { throw new DocumentException( $"TopicHref should be used to specify the homepage for {item.TocHref} when tocHref is used."); } if (tocHrefType == HrefType.RelativeFile || tocHrefType == HrefType.RelativeFolder) { throw new DocumentException($"TocHref {item.TocHref} only supports absolute path or local toc file."); } } var hrefType = Utility.GetHrefType(item.Href); switch (hrefType) { case HrefType.AbsolutePath: case HrefType.RelativeFile: if (item.Items != null && item.Items.Count > 0) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { var defaultItem = GetDefaultHomepageItem(item); if (defaultItem != null) { item.AggregatedHref = defaultItem.TopicHref; item.AggregatedUid = defaultItem.TopicUid; } } } if (string.IsNullOrEmpty(item.TopicHref)) { // Get homepage from TocHref if href/topicHref is null or empty if (string.IsNullOrEmpty(item.Href) && string.IsNullOrEmpty(item.TopicUid) && tocFileModel != null) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } // Use TopicHref in output model item.TopicHref = item.Href; } break; case HrefType.RelativeFolder: { if (tocFileModel != null) { Logger.LogWarning($"Href {item.Href} is overwritten by tocHref {item.TocHref}"); } else { var relativeFolder = (RelativePath)file.File + (RelativePath)item.Href; var tocFilePath = relativeFolder + (RelativePath)Constants.YamlTocFileName; var tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); // First, try finding toc.yml under the relative folder // Second, try finding toc.md under the relative folder if (!_collection.TryGetValue(tocFile, out tocFileModel)) { tocFilePath = relativeFolder + (RelativePath)Constants.MarkdownTocFileName; tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); if (!_collection.TryGetValue(tocFile, out tocFileModel)) { var message = $"Unable to find either {Constants.YamlTocFileName} or {Constants.MarkdownTocFileName} inside {item.Href}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); break; } } item.TocHref = tocFilePath - (RelativePath)file.File; } // Get homepage from TocHref if TopicHref/TopicUid is not specified if (string.IsNullOrEmpty(item.TopicHref) && string.IsNullOrEmpty(item.TopicUid)) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = item.TopicHref = resolved.TopicHref ?? resolved.AggregatedHref; item.TopicUid = resolved.TopicUid ?? resolved.AggregatedUid; } else { item.Href = item.TopicHref; } if (item.Items != null) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } } } break; case HrefType.MarkdownTocFile: case HrefType.YamlTocFile: { var tocFilePath = (RelativePath)file.File + (RelativePath)item.Href; var tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); TocItemInfo referencedTocFileModel; TocItemViewModel referencedToc; stack.Push(file); if (_collection.TryGetValue(tocFile, out referencedTocFileModel) || _notInProjectTocCache.TryGetValue(tocFile, out referencedTocFileModel)) { referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedTocFileModel.IsReferenceToc = true; referencedToc = referencedTocFileModel.Content; } else { // It is acceptable that the referenced toc file is not included in docfx.json, as long as it can be found locally referencedTocFileModel = new TocItemInfo(tocFile, new TocItemViewModel { Items = Utility.LoadSingleToc(tocFile.FullPath) }); referencedTocFileModel = ResolveItem(referencedTocFileModel, stack); referencedToc = referencedTocFileModel.Content; _notInProjectTocCache[tocFile] = referencedTocFileModel; } stack.Pop(); // For referenced toc, content from referenced toc is expanded as the items of current toc item, // Href is reset to the homepage of current toc item item.Href = item.TopicHref; item.Items = referencedToc.Items; } break; default: break; } var relativeToFile = (RelativePath)file.File; item.OriginalHref = item.Href; item.OriginalTocHref = item.TocHref; item.OriginalTopicHref = item.TopicHref; item.OriginalHomepage = item.Homepage; item.Href = NormalizeHref(item.Href, relativeToFile); item.TocHref = NormalizeHref(item.TocHref, relativeToFile); item.TopicHref = NormalizeHref(item.TopicHref, relativeToFile); item.Homepage = NormalizeHref(item.Homepage, relativeToFile); wrapper.IsResolved = true; // for backward compatibility if (item.Href == null && item.Homepage == null) { item.Href = item.TocHref; item.Homepage = item.TopicHref; } return(wrapper); }
private TocItemInfo ResolveItemCore(TocItemInfo wrapper, Stack <FileAndType> stack) { if (wrapper.IsResolved) { return(wrapper); } var file = wrapper.File; if (stack.Contains(file)) { throw new DocumentException($"Circular reference to {file.FullPath} is found in {stack.Peek().FullPath}"); } var item = wrapper.Content; var hrefType = Utility.GetHrefType(item.Href); switch (hrefType) { case HrefType.AbsolutePath: case HrefType.RelativeFile: if (item.Items != null && item.Items.Count > 0) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } if (string.IsNullOrEmpty(item.Homepage) && string.IsNullOrEmpty(item.HomepageUid)) { var defaultItem = GetDefaultHomepageItem(item); if (defaultItem != null) { item.Homepage = defaultItem.Href; item.HomepageUid = defaultItem.Uid; } } } break; case HrefType.RelativeFolder: { var relativeFolder = (RelativePath)file.File + (RelativePath)item.Href; var tocFilePath = relativeFolder + (RelativePath)Constants.YamlTocFileName; var tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); TocItemInfo tocFileModel; // First, try finding toc.yml under the relative folder // Second, try finding toc.md under the relative folder if (!_collection.TryGetValue(tocFile, out tocFileModel)) { tocFilePath = relativeFolder + (RelativePath)Constants.MarkdownTocFileName; tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); if (!_collection.TryGetValue(tocFile, out tocFileModel)) { var message = $"Unable to find either {Constants.YamlTocFileName} or {Constants.MarkdownTocFileName} inside {item.Href}. Make sure the file is included in config file docfx.json!"; Logger.LogWarning(message); break; } } item.TocHref = tocFilePath - (RelativePath)file.File; // Get homepage from the referenced toc if (string.IsNullOrEmpty(item.Homepage) && string.IsNullOrEmpty(item.HomepageUid)) { stack.Push(file); var resolved = ResolveItem(tocFileModel, stack).Content; stack.Pop(); item.Href = resolved.Homepage; item.Uid = resolved.HomepageUid; } else { // Set homepage to href item.Href = item.Homepage; item.Uid = item.HomepageUid; } if (item.Items != null) { for (int i = 0; i < item.Items.Count; i++) { item.Items[i] = ResolveItem(new TocItemInfo(file, item.Items[i]), stack).Content; } } } break; case HrefType.MarkdownTocFile: case HrefType.YamlTocFile: { var tocFilePath = (RelativePath)file.File + (RelativePath)item.Href; var tocFile = new FileAndType(file.BaseDir, tocFilePath, file.Type, file.PathRewriter); TocItemInfo tocFileModel; TocItemViewModel referencedToc; stack.Push(file); if (_collection.TryGetValue(tocFile, out tocFileModel) || _notInProjectTocCache.TryGetValue(tocFile, out tocFileModel)) { tocFileModel = ResolveItem(tocFileModel, stack); tocFileModel.IsReferenceToc = true; referencedToc = tocFileModel.Content; } else { // It is acceptable that the referenced toc file is not included in docfx.json, as long as it can be found locally tocFileModel = new TocItemInfo(tocFile, new TocItemViewModel { Items = Utility.LoadSingleToc(tocFile.FullPath) }); tocFileModel = ResolveItem(tocFileModel, stack); referencedToc = tocFileModel.Content; _notInProjectTocCache[tocFile] = tocFileModel; } stack.Pop(); // For referenced toc, content from referenced toc is expanded as the items of current toc item, // Href is reset to the homepage of current toc item item.Href = item.Homepage; item.Uid = item.HomepageUid; item.Items = referencedToc.Items; } break; default: break; } var relativeToFile = (RelativePath)file.File; item.Href = NormalizeHref(item.Href, relativeToFile); item.TocHref = NormalizeHref(item.TocHref, relativeToFile); item.Homepage = NormalizeHref(item.Homepage, relativeToFile); wrapper.IsResolved = true; return(wrapper); }