From 719758c57054223b5586a65fbbff639b79ad115e Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Fri, 23 Oct 2020 17:40:33 -0700 Subject: [PATCH] Markdown: recognize wiki style image links When the --wiki option is active, recognize wiki-style image links in the format: [[link-to-image.png|align=left,alt=text]] Where any "well-known" image suffix may be used in place of ".png" and the "|..." part is optional but may specify any of the "width=", "height=", "align=" or "alt=" keywords (provided alt= is always last). Signed-off-by: Kyle J. McKay --- Markdown.pl | 103 ++++++++++++++++++++++++++++++++++++++++++---------- syntax.md | 37 ++++++++++++++++++- 2 files changed, 118 insertions(+), 22 deletions(-) diff --git a/Markdown.pl b/Markdown.pl index 8704bc4..62cb7e8 100755 --- a/Markdown.pl +++ b/Markdown.pl @@ -660,8 +660,8 @@ sub ProcessRaw { # wikiopt => HASH ref of options affecting wiki links processing. # best set with SetWikiOpts (see SetWikiOpts comments). # wikifunc => if set to a CODE ref, the function will be called with -# five arguments like so: -# $result = &$wikifunc($iresult, \%options, $link, $wbase, $qf) +# six arguments like so: +# $result = &$wikifunc($iresult, \%opts, $link, $wbase, $qf, $io) # where on input $iresult is the result that would be produced # if no wikifunc was provided and on output $result will be # used as the wiki expansion. $link is the original wiki @@ -670,8 +670,18 @@ sub ProcessRaw { # and then transforming that according to the wikiopt HASH ref. # $qf contains either an empty string or the stripped off # query string and/or fragment if one was originally present. +# If $io is a HASH ref (otherwise it will be undef), then it's +# a wiki image link and %$io contains the options (if any) where +# the keys that might be present include "width", "height", "align" +# and "alt". If present, width and height are guaranteed to be +# positive integers and align is guaranteed to be "left", "right" +# or "center". The value for "alt" may be the empty string. +# The "imgflag" key has a SCALAR ref value and if the value it +# refers to is changed to any false value the result will become +# an A tag rather than an IMG tag. # The $iresult value is related to the other arguments like so: -# $iresult = $options->{wikipat}; +# $iresult = $opts->{wikipat}; +# $iresult =~ s/%\{\}.+$/%{}/ if ref($io) eq "HASH"; # $iresult = s/%\{\}/$wbase/; # $iresult .= $qf; # Any provided wikifunc should treat the %options HASH as @@ -1207,13 +1217,25 @@ sub _ProcessWikiLink { } if ($opt{wikipat}) { my $o = $opt{wikiopt}; + my $img_link = _strip($link_text); + my $img = 0; my $qsfrag = ""; - my $base = $link_loc; - if ($link_loc =~ /^(.*?)([?#].*)$/os) { - ($base, $qsfrag) = ($1, $2); + my $base; + my $imgopts = undef; + if ($img_link =~ /^[^#?\s]+\.(?:png|gif|jpe?g|svgz?)$/i) { + $base = _wxform($img_link, 1); + $img = 1; + $imgopts = _ParseWikiImgOpts($link_loc); + $imgopts->{imgflag} = \$img; + } else { + $base = $link_loc; + if ($link_loc =~ /^(.*?)([?#].*)$/os) { + ($base, $qsfrag) = ($1, $2); + } + $base = _wxform($base); } - $base = _wxform($base); my $result = $opt{wikipat}; + $result =~ s/%\{\}.+$/%{}/os if $img; $result =~ s/%\{\}/$base/; if ($qsfrag =~ /^([^#]*)(#.+)$/os) { my ($q,$f) = ($1,$2); @@ -1221,7 +1243,7 @@ sub _ProcessWikiLink { $qsfrag = $q . $f; } $result .= $qsfrag; - $result = &{$opt{wikifunc}}($result, \%opt, $link_loc, $base, $qsfrag) + $result = &{$opt{wikifunc}}($result, \%opt, ($img?$img_link:$link_loc), $base, $qsfrag, $imgopts) if ref($opt{wikifunc}) eq 'CODE'; { use bytes; @@ -1236,18 +1258,44 @@ sub _ProcessWikiLink { $result =~ s/(%(?![0-9A-F]{2})[0-9A-Fa-f]{2})/uc($1)/soge; } # Return the new link - return _MakeATag($result, $link_text); + return $img ? _MakeIMGTag($result, undef, undef, $imgopts) : _MakeATag($result, $link_text); } # leave it alone return undef; } +sub _ParseWikiImgOpts { + my $alts = shift; + my %o = (); + # alt= consumes the rest of the line, do it first + if ($alts =~ /(?:^|,)\s*alt\s*=\s*(.*)$/ios) { + my $atext = $1; + $alts = substr($alts, 0, $-[0]); + $o{alt} = _strip($atext); + } + foreach my $kv (split(/\s*,\s*/, lc($alts))) { + if ($kv =~ /^\s*([^\s]+)\s*=\s*([^\s]+)\s*$/os) { + my ($k, $v) = ($1, $2); + if (($k eq "width" || $k eq "height") && $v =~ /^\d+$/) { + $o{$k} = 0+$v if $v > 0; + next; + } + if ($k eq "align" && ($v eq "left" || $v eq "right" || $v eq "center")) { + $o{$k} = $v; + next; + } + } + } + return \%o; +} + + sub _wxform { - my $w = shift; + my ($w, $img) = @_; my $o = $opt{wikiopt}; my $opt_s = $o->{s}; - if ($opt_s) { + if (!$img && $opt_s) { if (ref($opt_s)) { if ($w =~ m{^(.*)[.]([^./]*)$}) { my ($base, $ext) = ($1, $2); @@ -1495,10 +1543,11 @@ sub _FindFragmentMatch { # 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 sub _MakeIMGTag { - my ($url, $alt, $title) = @_; + my ($url, $alt, $title, $iopts) = @_; defined($url) or $url=""; defined($alt) or $alt=""; defined($title) or $title=""; + ref($iopts) eq "HASH" or $iopts = {}; return "" unless $url ne ""; my ($w, $h, $lf, $rt) = (0, 0, '', ''); @@ -1512,20 +1561,28 @@ sub _MakeIMGTag { } elsif ($title =~ /^(.*)\((?!\))(?)\)$/os) { ($title, $lf, $rt) = (_strip($1), $2, $3); } + $iopts->{align} = "center" if $lf && $rt; + $iopts->{align} = "left" if $lf && !$rt; + $iopts->{align} = "right" if !$lf && $rt; + $iopts->{width} = $w if $w != 0; + $iopts->{height} = $h if $h != 0; + $iopts->{alt} = $alt if $alt ne ""; + $iopts->{title} = $title if $title ne ""; + my $iopt = sub { defined($iopts->{$_[0]}) ? $iopts->{$_[0]} : (@_ > 1 ? $_[1] : "") }; my $result = ''; $result .= $g_escape_table{'<'}."center".$g_escape_table{'>'} - if $lf && $rt; + if &$iopt("align") eq "center"; $result .= $g_escape_table{'<'}."img src=\"" . _EncodeAttText($url) . "\""; - $result .= " align=\"left\"" if $lf && !$rt; - $result .= " align=\"right\"" if $rt && !$lf; - $result .= " alt=\"" . _EncodeAttText($alt) . "\"" if $alt ne ""; - $result .= " width=\"$w\"" if $w != 0; - $result .= " height=\"$h\"" if $h != 0; - $result .= " title=\"" . _EncodeAttText($title) . "\"" if $title ne ""; + $result .= " align=\"left\"" if &$iopt("align") eq "left"; + $result .= " align=\"right\"" if &$iopt("align") eq "right"; + $result .= " alt=\"" . _EncodeAttText($iopts->{alt}) . "\"" if &$iopt("alt") ne ""; + $result .= " width=\"" . $iopts->{width} . "\"" if &$iopt("width",0) != 0; + $result .= " height=\"" . $iopts->{height} . "\"" if &$iopt("height",0) != 0; + $result .= " title=\"" . _EncodeAttText($iopts->{title}) . "\"" if &$iopt("title") ne ""; $result .= " /" unless $opt{empty_element_suffix} eq ">"; $result .= $g_escape_table{'>'}; $result .= $g_escape_table{'<'}."/center".$g_escape_table{'>'} - if $lf && $rt; + if &$iopt("align") eq "center"; return $result; } @@ -3863,6 +3920,8 @@ followed by one of the case-insensitive I<< >> values. As a special case, using the value C<:md> for one of the I<< >> values causes that value to be expanded to all known markdown extensions. +When processing wiki image links, this option is ignored. + =item B Convert link target (excluding any query string and/or fragment) to UPPERCASE. @@ -3889,6 +3948,10 @@ sequence) replaced with this computed value and the original query string and/or fragment is re-appended (if any were originally present) and URL-encoding is applied as needed to produce the actual final target URL. +Note that when processing wiki image links, no extension stripping ever takes +place (i.e. the "s" option is ignored) and anything after the placeholder (the +C<%{...}> sequence) in the pattern is omitted from the result. + See above option descriptions for possible available modifications. One of the commonly used hosting platforms does something substantially similar diff --git a/syntax.md b/syntax.md index ae45e9c..1c3c751 100644 --- a/syntax.md +++ b/syntax.md @@ -823,7 +823,7 @@ Markdown supports two style of links: *inline* and *reference* by default. In both styles, the link text is delimited by [square brackets]. -Additionally, if enabled, Wiki Style Links are also supported, but +Additionally, if enabled, [Wiki Style Links] are also supported, but they are delimited by doubled square brackets (e.g. `[[wiki link]]`) and have different semantics -- see the end of this section for that. @@ -1064,8 +1064,41 @@ links above would generate these "a" tags: elsewhere#section link here +If full wiki style links have been enabled (via the `--wiki` option), +image links may be created using the wiki syntax like so: + + [[some-image.png]] + [[other-image.jpg|alt=text for alt]] + [[image-left.svg|align=left]] + [[image-on-right.jpeg|align=right]] + [[in-a-div.gif|align=center]] + [[image-right.svg|align=left,alt=text for image]] + [[scaled.svg|width=200,height=100,alt=scaled image]] + +For a wiki style image link to be recognized, the "link" part (which +is just the part to the left of the `|` if it's present), must: + + * not have any embedded spaces (leading/trailing will be stripped) + * must end in a well-known image suffix (case insensitively) + +Currently only `.png`, `.gif`, `.jpg`, `.jpeg`, `.svg` and `.svgz` +are recognized as "well-known image suffixes". + +If the optional "|..." part is present for a wiki image link, then +the "alt=" part must be at the end as it will consume all the +remaining text. Currently only the "align=", "width=", "height=" +and "alt=" keywords are recognized. Keywords are comma (",") +separated (with optional surrounding whitespace). Note that width +and height are in pixels. + +Using either "left" or "right" for the "align=" keyword causes the +image to be floated either left or right respectively. Using +"center" for the "align=" keyword causes the image to be placed in +its own "div" with a "center" alignment. + See the command line help (`Markdown.pl --help`) for more details -on exactly how the wiki style links are transformed into "a" tags. +on exactly how the wiki style links are transformed into "a"/"img" +tags. ~~~~~~~~