Skip to content

Instantly share code, notes, and snippets.

@rickbutterfield
Created July 11, 2023 15:05
Show Gist options
  • Save rickbutterfield/7e567d689e41102eb8228cc0fa1514d1 to your computer and use it in GitHub Desktop.
Save rickbutterfield/7e567d689e41102eb8228cc0fa1514d1 to your computer and use it in GitHub Desktop.
public static class StringExtensionMethods
{
public static string AddHeadingAnchorsToHtml(this string html)
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
// select all possible headings in the document
var headings = doc.DocumentNode.SelectNodes("//h2");
if (headings != null)
{
foreach (var heading in headings)
{
var headingText = heading.InnerText;
// if heading has id, use it
string headingId = heading.Attributes["id"]?.Value;
if (headingId == null)
{
// if heading does not have an id, generate a safe id by creating a slug based on the heading text
// slug is a URL/SEO friendly part of a URL, this is a good option for generating anchor fragments
// Source: http://predicatet.blogspot.com/2009/04/improved-c-slug-generator-or-how-to.html
// assumption: Prase should only contain standard a-z characters or numbers
headingId = headingText.ToCssClass();
// for the fragment to work (jump to the relevant content), the heading id and fragment needs to match
heading.Attributes.Append("id", headingId);
heading.Attributes.Append("class", "anchor");
}
// use a non-breaking space to make sure the heading text and the #-sign don't appear on a separate line
//heading.InnerHtml += " ";
// create the heading anchor which points to the heading
//var headingAnchor = HtmlNode.CreateNode($"<a href=\"#{headingId}\" aria-label=\"Anchor for heading: {headingText}\">#</a>");
// append the anchor behind the heading text content
//heading.AppendChild(headingAnchor);
}
}
return doc.DocumentNode.InnerHtml;
}
public static List<KeyValuePair<string, string>> GetHeadingAnchorsFromHtml(this IHtmlEncodedString html)
{
List<KeyValuePair<string, string>> headingData = new();
var doc = new HtmlDocument();
doc.LoadHtml(html.ToHtmlString());
// select all possible headings in the document
var headings = doc.DocumentNode.SelectNodes("//h2");
if (headings != null)
{
foreach (var heading in headings)
{
var headingText = heading.InnerText;
// if heading has id, use it
string headingId = heading.Attributes["id"]?.Value;
if (headingId == null)
{
// if heading does not have an id, generate a safe id by creating a slug based on the heading text
// slug is a URL/SEO friendly part of a URL, this is a good option for generating anchor fragments
// Source: http://predicatet.blogspot.com/2009/04/improved-c-slug-generator-or-how-to.html
// assumption: Prase should only contain standard a-z characters or numbers
headingId = headingText.ToCssClass();
// for the fragment to work (jump to the relevant content), the heading id and fragment needs to match
}
headingData.Add(new KeyValuePair<string, string>(headingId, headingText));
}
}
return headingData;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment