Skip to content

Commit

Permalink
support anchor in xref #190
Browse files Browse the repository at this point in the history
  • Loading branch information
superyyrrzz committed Apr 1, 2016
1 parent 468b2dc commit 051548b
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 80 deletions.
22 changes: 14 additions & 8 deletions src/Microsoft.DocAsCode.Build.Engine/HostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,17 @@ private MarkupResult MarkupCore(string markdown, FileAndType ft)
node.Remove();
}
var linkToFiles = new HashSet<string>();
foreach (var link in from n in doc.DocumentNode.Descendants()
where !string.Equals(n.Name, "xref", StringComparison.OrdinalIgnoreCase)
from attr in n.Attributes
where string.Equals(attr.Name, "src", StringComparison.OrdinalIgnoreCase) ||
string.Equals(attr.Name, "href", StringComparison.OrdinalIgnoreCase)
where !string.IsNullOrWhiteSpace(attr.Value)
select attr)
foreach (var pair in (from n in doc.DocumentNode.Descendants()
where !string.Equals(n.Name, "xref", StringComparison.OrdinalIgnoreCase)
from attr in n.Attributes
where string.Equals(attr.Name, "src", StringComparison.OrdinalIgnoreCase) ||
string.Equals(attr.Name, "href", StringComparison.OrdinalIgnoreCase)
where !string.IsNullOrWhiteSpace(attr.Value)
select new { Node = n, Attr = attr }).ToList())
{
string linkFile;
string anchor = null;
var link = pair.Attr;
if (PathUtility.IsRelativePath(link.Value))
{
var index = link.Value.IndexOf('#');
Expand All @@ -130,7 +131,12 @@ where string.Equals(attr.Name, "src", StringComparison.OrdinalIgnoreCase) ||
}
var path = (RelativePath)ft.File + (RelativePath)linkFile;
var file = path.GetPathFromWorkingFolder();
link.Value = file + anchor;
link.Value = file;
if (!string.IsNullOrEmpty(anchor) &&
string.Equals(link.Name, "href", StringComparison.OrdinalIgnoreCase))
{
pair.Node.SetAttributeValue("anchor", anchor);
}
linkToFiles.Add(HttpUtility.UrlDecode(file));
}
}
Expand Down
37 changes: 24 additions & 13 deletions src/Microsoft.DocAsCode.Build.Engine/TemplateProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ private static void TranformHtml(IDocumentBuildContext context, string transform
HtmlAgilityPack.HtmlDocument html = new HtmlAgilityPack.HtmlDocument();
html.LoadHtml(transformed);

var xrefLinkNodes = html.DocumentNode.SelectNodes("//a[starts-with(@href, 'xref:')]");
if (xrefLinkNodes != null)
{
foreach (var xref in xrefLinkNodes)
{
TransformXrefLink(xref, context);
}
}

var xrefExceptions = new List<CrossReferenceNotResolvedException>();
var xrefNodes = html.DocumentNode.SelectNodes("//xref/@href");
if (xrefNodes != null)
Expand Down Expand Up @@ -395,6 +404,12 @@ private void ProcessSingleDependency(Stream stream, string outputDirectory, stri
}
}

private static void TransformXrefLink(HtmlAgilityPack.HtmlNode node, IDocumentBuildContext context)
{
var convertedNode = XrefDetails.ConvertXrefLinkNodeToXrefNode(node);
node.ParentNode.ReplaceChild(convertedNode, node);
}

private static void UpdateXref(HtmlAgilityPack.HtmlNode node, IDocumentBuildContext context, string language)
{
var xref = XrefDetails.From(node);
Expand All @@ -403,11 +418,10 @@ private static void UpdateXref(HtmlAgilityPack.HtmlNode node, IDocumentBuildCont
// Internal one overrides external one
var xrefSpec = context.GetXrefSpec(xref.Uid);
xref.ApplyXrefSpec(xrefSpec);
bool resolved = xrefSpec != null;

var convertedNode = xref.ConvertToHtmlNode(language);
node.ParentNode.ReplaceChild(convertedNode, node);
if (!resolved)
if (xrefSpec == null)
{
if (xref.ThrowIfNotResolved)
{
Expand All @@ -423,21 +437,18 @@ private static void UpdateHref(HtmlAgilityPack.HtmlNode link, string attribute,
if (PathUtility.TryGetPathFromWorkingFolder(key, out path))
{
string href;
// For href, # may be appended, remove # before search file from map
var anchorIndex = key.IndexOf("#");
var anchor = string.Empty;
if (anchorIndex == 0) return;
if (anchorIndex > 0)
{
anchor = key.Substring(anchorIndex);
key = key.Remove(anchorIndex);
}

href = context.GetFilePath(HttpUtility.UrlDecode(key));
href = context.GetFilePath(key);
if (href != null)
{
href = ((RelativePath)UpdateFilePath(href, relativePath)).UrlEncode();
href += anchor;

var anchor = link.GetAttributeValue("anchor", null);
if (!string.IsNullOrEmpty(anchor))
{
href += anchor;
link.Attributes.Remove(link.Attributes["anchor"]);
}
link.SetAttributeValue(attribute, href);
}
else
Expand Down
71 changes: 62 additions & 9 deletions src/Microsoft.DocAsCode.Build.Engine/XrefDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.DocAsCode.Build.Engine
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;

using Microsoft.DocAsCode.MarkdownLite;
using Microsoft.DocAsCode.Plugins;
Expand All @@ -22,6 +23,7 @@ internal sealed class XrefDetails
private static Regex HtmlEncodeRegex = new Regex(@"\W", RegexOptions.Compiled);

public string Uid { get; private set; }
public string Anchor { get; private set; }
public string PlainTextDisplayName { get; private set; }
public string AnchorDisplayName { get; private set; }
public string Title { get; private set; }
Expand All @@ -36,7 +38,23 @@ public static XrefDetails From(HtmlAgilityPack.HtmlNode node)
{
if (node.Name != "xref") throw new NotSupportedException("Only xref node is supported!");
var xref = new XrefDetails();
xref.Uid = node.GetAttributeValue("href", null);

var rawUid = node.GetAttributeValue("href", null);
if (!string.IsNullOrEmpty(rawUid))
{
var anchorIndex = rawUid.IndexOf("#");
if (anchorIndex == -1)
{
xref.Anchor = string.Empty;
xref.Uid = HttpUtility.UrlDecode(rawUid);
}
else
{
xref.Anchor = rawUid.Substring(anchorIndex);
xref.Uid = HttpUtility.UrlDecode(rawUid.Remove(anchorIndex));
}
}

var overrideName = node.InnerText;
if (!string.IsNullOrEmpty(overrideName))
{
Expand All @@ -52,7 +70,19 @@ public static XrefDetails From(HtmlAgilityPack.HtmlNode node)
}

xref.Title = node.GetAttributeValue("title", null);
xref.Raw = node.GetAttributeValue("data-raw", null);

// Both `data-raw-html` and `data-raw` are html encoded. Use `data-raw-html` with higher priority.
// `data-raw-html` will be decoded then displayed, while `data-raw` will be displayed directly.
var raw = node.GetAttributeValue("data-raw-html", null);
if (!string.IsNullOrEmpty(raw))
{
xref.Raw = StringHelper.HtmlDecode(raw);
}
else
{
xref.Raw = node.GetAttributeValue("data-raw", null);
}

xref.ThrowIfNotResolved = node.GetAttributeValue("data-throw-if-not-resolved", false);

return xref;
Expand All @@ -64,16 +94,14 @@ public void ApplyXrefSpec(XRefSpec spec)
var href = spec.Href;
if (PathUtility.IsRelativePath(href))
{
var hashtagIndex = href.IndexOf('#');
if (hashtagIndex == -1)
if (string.IsNullOrEmpty(Anchor))
{
// TODO: hashtag from tempalte
var htmlId = GetHtmlId(Uid);
// TODO: What if href is not html?
href = href + "#" + htmlId;
Anchor = "#" + GetHtmlId(Uid);
}
}
Href = href;
Href = HttpUtility.UrlDecode(href);
Spec = spec;
}

Expand All @@ -95,7 +123,7 @@ public HtmlAgilityPack.HtmlNode ConvertToHtmlNode(string language)
value = StringHelper.HtmlEncode(GetLanguageSpecificAttribute(Spec, language, value, "name"));
}
}
return GetAnchorNode(Href, Title, value);
return GetAnchorNode(Href, Anchor, Title, value);
}
else
{
Expand Down Expand Up @@ -124,9 +152,13 @@ public static string GetHtmlId(string id)
return HtmlEncodeRegex.Replace(id, "_");
}

private static HtmlAgilityPack.HtmlNode GetAnchorNode(string href, string title, string value)
private static HtmlAgilityPack.HtmlNode GetAnchorNode(string href, string anchor, string title, string value)
{
var anchorNode = $"<a class=\"xref\" href=\"{href}\"";
if (!string.IsNullOrEmpty(anchor))
{
anchorNode += $" anchor=\"{anchor}\"";
}
if (!string.IsNullOrEmpty(title))
{
anchorNode += $" title=\"{title}\"";
Expand Down Expand Up @@ -164,5 +196,26 @@ private static string GetLanguageSpecificAttribute(XRefSpec spec, string languag

return defaultValue;
}

public static HtmlAgilityPack.HtmlNode ConvertXrefLinkNodeToXrefNode(HtmlAgilityPack.HtmlNode node)
{
var href = node.GetAttributeValue("href", null);
if (node.Name != "a" || string.IsNullOrEmpty(href) || !href.StartsWith("xref:"))
{
throw new NotSupportedException("Only anchor node with href started with \"xref:\" is supported!");
}
href = href.Substring("xref:".Length);
var raw = StringHelper.HtmlEncode(node.OuterHtml);
var title = node.GetAttributeValue("title", null);

var xrefNode = $"<xref href=\"{href}\" data-throw-if-not-resolved=\"True\" data-raw-html=\"{raw}\"";
if (!string.IsNullOrEmpty(title))
{
xrefNode += $" title=\"{title}\"";
}
xrefNode += $">{node.InnerText}</xref>";

return HtmlAgilityPack.HtmlNode.CreateNode(xrefNode);
}
}
}
1 change: 0 additions & 1 deletion src/Microsoft.DocAsCode.Dfm/DfmEngineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public DfmEngineBuilder(Options options, string baseDir = null) : base(options)
inlineRules.Insert(index + 1, new DfmEmailInlineRule());

// xref link inline rule must be before MarkdownLinkInlineRule
inlineRules.Insert(index, new DfmXrefLinkInlineRule());
inlineRules.Insert(index, new DfmIncludeInlineRule());

index = inlineRules.FindIndex(s => s is MarkdownTextInlineRule);
Expand Down
1 change: 0 additions & 1 deletion src/Microsoft.DocAsCode.Dfm/Microsoft.DocAsCode.Dfm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
<Compile Include="Rules\DfmSectionBlockRule.cs" />
<Compile Include="Rules\DfmTextInlineRule.cs" />
<Compile Include="Rules\DfmXrefAutoLinkInlineRule.cs" />
<Compile Include="Rules\DfmXrefLinkInlineRule.cs" />
<Compile Include="Rules\DfmXrefShortcutInlineRule.cs" />
<Compile Include="Rules\DfmYamlHeaderBlockRule.cs" />
<Compile Include="Tokens\DfmFencesBlockToken.cs" />
Expand Down
37 changes: 0 additions & 37 deletions src/Microsoft.DocAsCode.Dfm/Rules/DfmXrefLinkInlineRule.cs

This file was deleted.

Loading

0 comments on commit 051548b

Please sign in to comment.