diff --git a/Markdown.pl b/Markdown.pl index f63a85c..0ddb0b2 100755 --- a/Markdown.pl +++ b/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