Browse Source

Markdown.pl: provide anchor API access

The actual anchor id values produced while processing a page
are not necessarily immediately obvious.

These implicit anchor id values are created for all markdown-
format H1-H6 headers by "processing" the text of the header.

Provide a new external function, ResolveFragment that can
hook up a fragment identifier to one of these automatically-
generated anchor id values by transforming it as needed.

The lookup table needed by ResolveFragment can be retrieved
after calling Markdown by first setting the 'anchors' key in
the passed in options HASH ref.

Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
master
Kyle J. McKay 3 years ago
parent
commit
6324b499f7
  1. 83
      Markdown.pl

83
Markdown.pl

@ -36,7 +36,7 @@ my ($hasxml, $hasxml_err); BEGIN { ($hasxml, $hasxml_err) = (0, "") }
my ($hasxmlp, $hasxmlp_err); BEGIN { ($hasxmlp, $hasxmlp_err) = (0, "") }
@ISA = qw(Exporter);
@EXPORT_OK = qw(Markdown ProcessRaw GenerateStyleSheet SetWikiOpts SplitURL
escapeXML unescapeXML);
escapeXML unescapeXML ResolveFragment);
$INC{__PACKAGE__.'.pm'} = $INC{basename(__FILE__)} unless exists $INC{__PACKAGE__.'.pm'};
close(DATA) if fileno(DATA);
@ -751,6 +751,7 @@ sub ProcessRaw {
# fancy style sheet when calling Markdown directly.
# auto_number => <= 0 (default) no numbering, 1 number h1s,
# 2 number h1s, h2s, 3 number h1-h3s, ... >= 6 number h1-h6s
# anchors => existence of this key triggers return of anchors HASH
# yamlmode => 0 (no YAML processing), > 0 (YAML on), < 0 (YAML ignore)
# if 0, the YAML front matter processor is completely
# disabled and any YAML front matter that might be present
@ -827,6 +828,18 @@ sub ProcessRaw {
# The following are OUTPUT values that can only be retrieved when
# Markdown is called with a HASH ref as the second argument
#
# anchors => if the 'anchors' key exists in the input HASH ref
# will be set to a HASH ref containing lookup keys
# for valid fragment ids in the document (only those
# created from Markdown markup) with the value the
# actual fragment link to use. Do not use this directly
# but pass it as the first argument to the ResolveFragment
# function to resolve a "fuzzy" fragment name to its
# actual fragment name in the generated output.
# NOTE: to activate return of anchors the 'anchors' key
# simply must exist in the input HASH ref passed to the
# Markdown function, its value will be replaced on output.
#
# h1 => will be set to the tag-stripped value of the first
# non-empty H1 generated by Markdown-style markup.
# note that literal <h1>...</h1> values are NOT picked up.
@ -912,6 +925,11 @@ sub _SanitizeOpts {
$o->{yamlmode} = -1 unless looks_like_number($o->{yamlmode});
$o->{yamlvis} = 0 unless looks_like_number($o->{yamlvis});
delete $o->{yaml};
# The anchors hash will only be returned if the key exists
# (the key's value doesn't matter), set the value to an empty
# HASH ref just in case to make sure it's always a HASH ref.
$o->{anchors} = {} if exists($o->{anchors});
}
my %_yamlopts;
@ -1064,6 +1082,7 @@ sub Markdown {
utf8::encode($text);
if (ref($_[0]) eq "HASH") {
${$_[0]}{anchors} = {%g_anchors_id} if exists(${$_[0]}{anchors});
if (defined($opt{h1}) && $opt{h1}) {
utf8::encode($opt{h1});
${$_[0]}{h1} = $opt{h1};
@ -1735,28 +1754,29 @@ sub _SplitUrlTitlePart {
}
sub _FindFragmentMatch {
my $url = shift;
sub _FindFragmentMatchInternal {
my ($anchors_id, $url, $undefifnomatch) = @_;
if (defined($url) && $url =~ /^#\S/) {
# try very hard to find a match
my $idbase = _strip(lc(substr($url, 1)));
my $idbase0 = $idbase;
my $id = _MakeAnchorId($idbase);
if (defined($g_anchors_id{$id})) {
$url = $g_anchors_id{$id};
$undefifnomatch and $url = undef;
if (defined($$anchors_id{$id})) {
$url = $$anchors_id{$id};
} else {
$idbase =~ s/-/_/gs;
$id = _MakeAnchorId($idbase);
if (defined($g_anchors_id{$id})) {
$url = $g_anchors_id{$id};
if (defined($$anchors_id{$id})) {
$url = $$anchors_id{$id};
} else {
$id = _MakeAnchorId($idbase0, 1);
if (defined($g_anchors_id{$id})) {
$url = $g_anchors_id{$id};
if (defined($$anchors_id{$id})) {
$url = $$anchors_id{$id};
} else {
$id = _MakeAnchorId($idbase, 1);
if (defined($g_anchors_id{$id})) {
$url = $g_anchors_id{$id};
if (defined($$anchors_id{$id})) {
$url = $$anchors_id{$id};
}
}
}
@ -1766,6 +1786,47 @@ sub _FindFragmentMatch {
}
sub _FindFragmentMatch {
return _FindFragmentMatchInternal(\%g_anchors_id, @_);
}
sub _ToUTF8 {
my $input = shift;
my $output;
if (Encode::is_utf8($input) || utf8::decode($input)) {
$output = $input;
} else {
$output = $encoder->decode($input, Encode::FB_DEFAULT);
}
return $output;
}
# $_[0] -> HASH ref of anchors (e.g. the "anchors" OUTPUT from Markdown)
# $_[1] -> fragment to resolve, may optionally start with '#'
# An empty string ("") or hash ("#") is returned as-is.
# returns undef if no match otherwise resolved fragment name
# which will start with a '#' if $_[1] started with '#' otherwise will not.
# This function can be used to connect up links to "implicit" anchors.
# All Markdown-format H1-H6 headers have an implicit anchor added
# based on the header item text. Passing that text to this function
# will cough up the matching implicit anchor if there is one.
sub ResolveFragment
{
my ($anchors, $frag) = @_;
defined($frag) or return undef;
$frag eq "" || $frag eq "#" and return $frag;
my $hadhash = ($frag =~ s/^#//);
$frag =~ /^\S/ or return undef;
ref($anchors) eq 'HASH' or return undef;
my $ans = _FindFragmentMatchInternal($anchors, '#'._ToUTF8($frag), 1);
$hadhash || !defined($ans) or $ans =~ s/^#//;
defined($ans) and utf8::encode($ans);
return $ans;
}
# Return a suitably encoded <img...> tag string
# On input NONE of $url, $alt or $title should be xmlencoded
# but $url should already be url-encoded if needed, but NOT g_escape_table'd

Loading…
Cancel
Save