private void WriteReturns(RenderingState state) { if (state.Id.Type == "JsMethod" || state.Returns == null) { return; } state.AppendString($"<span class=\"table-of-contents-item-inline-returns\"> : "); if (!string.IsNullOrWhiteSpace(state.Returns.Href) && !state.Returns.IsVirtual) { state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.CurrentPath, state.Returns.Href, "html"))}\">"); } var title = state.Returns.Title; // If this is a List<T> or IList<T>, transform the return value to read "List of T" or "IList of T" if (title.StartsWith("List<") || title.StartsWith("IList<")) { var prefix = title.Substring(0, title.IndexOf("<")); var generics = title.Substring(prefix.Length + 1).TrimEnd('>'); title = $"{prefix} of {generics}"; } state.AppendString(EscapeHtml(title)); if (!string.IsNullOrWhiteSpace(state.Returns.Href) && !state.Returns.IsVirtual) { state.AppendString($"</a>"); } state.AppendString($"</span>"); }
private async Task WriteValuesAsync(RenderingState state) { if ((state.Document.Entity.Values?.Count ?? 0) == 0) { return; } state.AppendString($"<section class=\"values\">"); state.AppendString($"<h3 id=\"section-values\">Possible Values</h3>"); state.AppendString($"<dl>"); foreach (var value in state.Document.Entity.Values) { state.AppendString($"<dt>"); state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, value.Uri, "html"))}\">"); state.AppendString(EscapeHtml(value.Title)); state.AppendString($"</a>"); state.AppendString($"</dt>"); state.AppendString($"<dd>"); if (!string.IsNullOrWhiteSpace(value.Comment?.Brief)) { var result = await RenderMarkdownAsync(value.Comment.Brief); state.MarkdownDeferredIds.Add(result.Id); state.AppendString(result.Html); } state.AppendString($"</dd>"); } state.AppendString($"</dl>"); state.AppendString($"</section>"); }
private void WriteAttachedBy(RenderingState state) { if (!state.Id.Type.StartsWith("AttachedUx")) { return; } if (!state.EntityCache.ContainsKey(state.Uri.Href)) { throw new ArgumentException($"TOC item {state.Uri.Href} was not found in entity cache"); } var underlyingEntity = state.EntityCache[state.Uri.Href]; if (!state.EntityCache.ContainsKey(underlyingEntity.Id.ParentId)) { throw new ArgumentException($"Parent of TOC item {state.Id.Id} ({underlyingEntity.Id.ParentId} was not found in entity cache"); } var attachedBy = state.EntityCache[underlyingEntity.Id.ParentId]; var attachedByName = attachedBy.Titles.IndexTitle; var attachedByHref = attachedBy.Uri.Href; state.AppendString($"<span class=\"table-of-contents-item-inline-attached-by\">"); state.AppendString($"(attached by <a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.CurrentPath, attachedByHref, "html"))}\">{EscapeHtml(attachedByName)}</a>)"); state.AppendString($"</span>"); }
private async Task WriteTocByDeclaredInAsync(List <TocDeclaredInGroup> groups, RenderingState state) { state.AppendString($"<h3 id=\"section-table-of-contents\">"); state.AppendString($"Interface of {EscapeHtml(state.Document.Entity.Titles.IndexTitle)}"); state.AppendString($"</h3>"); foreach (var group in groups) { var hasAdvanced = group.Items.Any(e => e.IsAdvanced(state.Document.Entity.Id)); var onlyAdvanced = group.Items.All(e => e.IsAdvanced(state.Document.Entity.Id)); var cssClasses = new List <string> { "table-of-contents-section" }; if (hasAdvanced) { cssClasses.Add("has-advanced-items"); } if (onlyAdvanced) { cssClasses.Add("only-advanced-items"); } if (group.DeclaredIn != null && group.DeclaredIn.Uri != null && group.DeclaredIn.Uri.IdUri != state.Document.Entity.Uri.IdUri) { cssClasses.Add("inherited"); } if (group.Attached) { cssClasses.Add("attached"); } state.AppendString($"<section class=\"{string.Join(" ", cssClasses)}\">"); if (group.DeclaredIn != null && group.DeclaredIn.Id != null && group.DeclaredIn.Id.Id != state.Document.Entity.Id.Id) { state.AppendString($"<h4 id=\"section-table-of-contents-inherited-from-{group.DeclaredIn.Uri.Href.Replace("/", "-")}\">"); state.AppendString($"Inherited from"); state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, group.DeclaredIn.Uri.Href, "html"))}\">"); state.AppendString(EscapeHtml(group.DeclaredIn.Titles.IndexTitle)); state.AppendString($"</a>"); state.AppendString($"</h4>"); } else if (group.Attached) { state.AppendString($"<h4 id=\"section-table-of-contents-attached-ux-attributes\">Attached UX Attributes</h4>"); } foreach (var item in group.Items) { var result = await _tocRenderer.RenderAsync(item, state.EntityCache, state.Document.Entity.Uri.Href, item.IsAdvanced(state.Document.Entity.Id)); state.MarkdownDeferredIds.AddRange(result.DeferredMarkdownIds); state.AppendString(result.Html); state.Quality.TableOfContentsCommentLines.Add(item.Uri.IdUri, result.NumberOfCommentLines); } state.AppendString($"</section>"); } }
private async Task WriteParametersAsync(RenderingState state) { var parameters = BuildParameterCollection(state.Document.Entity); if (parameters.Count == 0) { return; } state.AppendString($"<section class=\"parameters\">"); state.AppendString($"<h3 id=\"section-parameters\">Parameters</h3>"); state.AppendString($"<dl>"); foreach (var param in parameters) { state.AppendString($"<dt>{EscapeHtml(param.Name)}</dt>"); state.AppendString($"<dd>"); if (!string.IsNullOrWhiteSpace(param.ReturnsTitle)) { state.AppendString($"<p>"); if (!string.IsNullOrWhiteSpace(param.ReturnsHref)) { state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, param.ReturnsHref, "html"))}\">"); } state.AppendString(EscapeHtml(param.ReturnsTitle)); if (!string.IsNullOrWhiteSpace(param.ReturnsHref)) { state.AppendString($"</a>"); } state.AppendString("</p>"); } else if (!string.IsNullOrWhiteSpace(param.TypeHint)) { state.AppendString($"<p>{EscapeHtml(param.TypeHint)}</p>"); } if (!string.IsNullOrWhiteSpace(param.Comment)) { var result = await RenderMarkdownAsync(param.Comment); state.MarkdownDeferredIds.Add(result.Id); state.AppendString(result.Html); } state.AppendString($"</dd>"); } state.AppendString($"</dl>"); state.AppendString($"</section>"); }
private void WriteAttachedAttributeDetails(RenderingState state) { var attachedInfo = ApiRenderingHelper.GetAttachedAttributeInfo(state.Document.Entity.Id, state.Document.Entity.Titles, state.Document.Entity.Parameters, state.Document.Entity.Attributes, state.EntityCache); if (attachedInfo == null) { return; } state.AppendString($"<p><em>"); state.AppendString($"Attached by <a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, attachedInfo.AttachedByHref, "html"))}\">{EscapeHtml(attachedInfo.AttachedByType)}</a>."); state.AppendString($"Use full name <code>{EscapeHtml(attachedInfo.FullName)}</code> in UX markup if ambiguous."); state.AppendString("</em></p>"); }
private void WriteLink(RenderingState state) { var title = ApiRenderingHelper.GetTitle(state.Id, state.Title, state.Comment, null, null, new Dictionary <string, ApiReferenceEntity>(), true); if (string.IsNullOrWhiteSpace(title)) { throw new ArgumentException($"No title could be generated for {state.Id.Id} in {state.CurrentPath} (type {state.Id.Type})"); } state.AppendString($"<h5>"); state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.CurrentPath, state.Uri.Href, "html"))}\">"); state.AppendString(EscapeHtml(title)); state.AppendString($"</a>"); WriteAttachedBy(state); WriteReturns(state); WriteLanguage(state); state.AppendString($"</h5>"); }
private async Task WriteReturnsAsync(RenderingState state) { var returns = BuildReturns(state.Document.Entity); if (returns == null) { return; } state.AppendString($"<section class=\"returns\">"); state.AppendString($"<h3 id=\"section-returns\">Returns</h3>"); state.AppendString($"<p>"); if (!string.IsNullOrWhiteSpace(returns.ReturnsTitle)) { if (!string.IsNullOrWhiteSpace(returns.ReturnsHref)) { state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, returns.ReturnsHref, "html"))}\">"); } state.AppendString(EscapeHtml(returns.ReturnsTitle)); if (!string.IsNullOrWhiteSpace(returns.ReturnsHref)) { state.AppendString($"</a>"); } } else if (!string.IsNullOrWhiteSpace(returns.TypeHint)) { state.AppendString(EscapeHtml(returns.TypeHint)); } state.AppendString($"</p>"); if (!string.IsNullOrWhiteSpace(returns.Comment)) { var result = await RenderMarkdownAsync(returns.Comment); state.MarkdownDeferredIds.Add(result.Id); state.AppendString(result.Html); } state.AppendString($"</section>"); }
private string RewriteTypePrefixedLinks(string input, string currentPath) { var prefixes = new[] { "articles", "api" }; var links = _linkCollector.GetLinksFrom(input); foreach (var prefix in prefixes) { var linksForPrefix = links.Where(e => e.StartsWith($"{prefix}:")) .OrderByDescending(e => e.Length); foreach (var link in linksForPrefix) { var path = link.Substring(prefix.Length + 1); var relativePath = RelativePathHelper.GetRelativePath(currentPath, path, null); var fragmentIdentifier = ""; { var hashIndex = relativePath.IndexOf('#'); if (hashIndex >= 0) { fragmentIdentifier = relativePath.Substring(hashIndex); relativePath = relativePath.Substring(0, hashIndex); } } if (relativePath.EndsWith(".md")) { relativePath = relativePath.Substring(0, relativePath.Length - 2) + "html"; } else if (relativePath.EndsWith(".json")) { relativePath = relativePath.Substring(0, relativePath.Length - 4) + "html"; } if (!relativePath.EndsWith(".html")) { relativePath += ".html"; } input = input.Replace(link, relativePath + fragmentIdentifier); } } return(input); }
private void WriteLocation(RenderingState state) { if (string.IsNullOrWhiteSpace(state.Document.Entity.Location?.NamespaceUri) && string.IsNullOrWhiteSpace(state.Document.Entity.Location?.PackageName)) { return; } var cssClasses = new List <string> { "type-location" }; if (state.Document.TableOfContents.Count == 0) { cssClasses.Add("type-location-leaf"); } state.AppendString($"<section class=\"{string.Join(" ", cssClasses)}\">"); state.AppendString($"<h3 id=\"section-location\">Location</h3>"); state.AppendString($"<dl>"); if (!string.IsNullOrWhiteSpace(state.Document.Entity.Location.NamespaceUri)) { state.AppendString($"<dt>Namespace</dt>"); state.AppendString($"<dd>"); state.AppendString($"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.Document.Entity.Uri.Href, state.Document.Entity.Location.NamespaceUri, "html"))}\">"); state.AppendString(EscapeHtml(state.Document.Entity.Location.NamespaceTitle)); state.AppendString($"</a>"); state.AppendString($"</dd>"); } if (!string.IsNullOrWhiteSpace(state.Document.Entity.Location.PackageName)) { state.AppendString($"<dt>Package</dt>"); state.AppendString($"<dd>{EscapeHtml(state.Document.Entity.Location.PackageName + " " + state.Document.Entity.Location.PackageVersion)}</dd>"); } state.AppendString($"</dl>"); state.AppendString($"</section>"); }
private async Task WriteCommentAsync(RenderingState state) { if (string.IsNullOrWhiteSpace(state.Comment?.Brief)) { return; } state.NumberOfCommentLines = CountNumberOfLines(string.IsNullOrWhiteSpace(state.Comment?.Full) ? "" : state.Comment.Full); var brief = state.Comment.Brief; if (state.Comment.Brief != state.Comment.Full) { brief += $" <a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(state.CurrentPath, state.Uri.Href, "html"))}\" class=\"table-of-contents-item-has-more\" title=\"There is more information available for this entry\"><i class=\"fa fa-ellipsis-h\"></i></a>"; } state.AppendString($"<div class=\"table-of-contents-item-brief\">"); var parsed = await _markdown.DeferredRenderAsync(brief); state.DeferredMarkdownIds.Add(parsed.Id); state.AppendString(parsed.Html); state.AppendString($"</div>"); }
public Task <string> FormatAsync(string input, string currentPath) { Match match; var i = 0; do { i++; match = _lookupMatchPattern.Match(input); if (!match.Success) { break; } var exactMatch = match.Groups[0].Value; var keyword = exactMatch.TrimStart('@'); var title = keyword; // Hack: If the match is within a code block, replace the leading @ symbol with __CODE_BLOCK_SYMBOL_AT__ and then replace that back // after the run. This ensures that we don't mess with code blocks, and by replacing the @ we also make sure we don't get stuck // in an infinite loop here where it will just continue to match on this unhandled match every iteration. if (IsWithinCodeBlock(match.Index, input)) { input = input.Substring(0, match.Index) + "__CODE_BLOCK_SYMBOL_AT__" + input.Substring(match.Index + 1); continue; } // Handle legacy links @(Keyword) and @(Keyword:title) if (keyword.StartsWith("(") && keyword.EndsWith(")")) { keyword = keyword.TrimStart('(').TrimEnd(')'); title = keyword; if (title.Contains(":")) { keyword = keyword.Substring(0, keyword.IndexOf(":")); title = title.Substring(title.IndexOf(":") + 1); } } if (!_lookups.ContainsKey(keyword)) { _failedLookups.AddOrUpdate(exactMatch, new HashSet <string>(new[] { currentPath }), (kw, e) => { e.Add(currentPath); return(e); }); input = ReplaceReferenceMatch(match, input, title); continue; } var uri = _lookups[keyword]; var replacement = $"<a href=\"{EscapeHtml(RelativePathHelper.GetRelativePath(currentPath, uri, "html"))}\">{EscapeHtml(title)}</a>"; input = ReplaceReferenceMatch(match, input, replacement); // Just some stuff to test that this works since it's such a f****d up implementation if (i > 10000) { throw new Exception($"Problems parsing references in {currentPath} - inifinite recursion bug: {input}"); } } while (true); // Replace any __CODE_BLOCK_SYMBOL_AT__ values with @ to correct the content input = input.Replace("__CODE_BLOCK_SYMBOL_AT__", "@"); return(Task.FromResult(input)); }