Parsedown: image size dimension attribute

2020-01-01 00:00:00 IN1 , 2023-04-10 14:16:30 IN1


This class BParsedown as an extension for "Parsedown" reads optional image size dimensions like =50x50 in markdown syntax and builds a proper image tag with width and height attributes from it. It supports inline syntax as well as markdown per reference.

pos. tested with Parsedown Release: 1.7.4.

Examples

Inline

markdown

![Odyssey2001.png](http://example.com/Odyssey2001.png =100x100 "Odyssey 2001")

converts to

html

<img src="http://example.com/Odyssey2001.png" alt="Odyssey2001.png" title="Odyssey 2001" width="100" height="100">

Reference

markdown

![Odyssey2001.png][1]

[1]: http://example.com/Odyssey2001.png =100x100 "Odyssey 2001"

converts to

html

<img src="http://example.com/Odyssey2001.png" alt="Odyssey2001.png" title="Odyssey 2001" width="100" height="100">

Usage

just use class BParsedown instead of Parsedown, that's all.

$oParsedown = new BParsedown();

PHP Class

i also published this as a gist on github, so you can also download form there.

<?php

/**
 * ✔ pos. tested with Parsedown 1.7.4
 *
 * This allows usage of image dimensions this way:
 *      ![odyssee2001](http://example.com/Odyssey2001.png =100x100)
 *      ![odyssee2001](http://example.com/Odyssey2001.png =100x100 "Even with Title")
 *
 * @related
 * https://github.com/erusev/parsedown/issues/723
 * https://stackoverflow.com/a/21242579/2487859
 * @copyright ueffing.net
 * @author Guido K.B.W. Üffing <info@ueffing.net>
 * @license GNU GENERAL PUBLIC LICENSE Version 3.
 */
class BParsedown extends \Parsedown
{
    /**
     * @param $aExcerpt
     * @return array|void
     */
    protected function inlineImage($aExcerpt)
    {
        if ( ! isset($aExcerpt['text'][1]) or $aExcerpt['text'][1] !== '[')
        {
            return;
        }

        $aExcerpt['text'] = self::getRelevantMarkdown($aExcerpt['text']);
        $aExcerpt['text']= mb_substr($aExcerpt['text'], 1);

        // watch out for link
        $aLink = $this->inlineLink($aExcerpt);

        // grab infos from string...
        $sAlt = strtok(mb_substr(strchr($aExcerpt['text'], '['), 1), ']');
        $sTitle = strtok(mb_substr(strchr($aExcerpt['text'], '"'), 0), '"');
        $sUrl = current(self::getUrlFromString($aExcerpt['text']));
        $mStrChr = mb_strrchr($aExcerpt['text'], '=');
        list($iWidth, $iHeight) = (false !== $mStrChr) ? array_map('intval', explode('x', mb_substr($mStrChr, 1))) : array('', '');

        // ...or from Link
        (true === empty($sAlt) && isset($aLink['element']['text'])) ? $sAlt = $aLink['element']['text'] : false;
        (true === empty($sUrl) && isset($aLink['element']['attributes']['href'])) ? $sUrl = $aLink['element']['attributes']['href'] : false;
        (true === empty($sTitle) && isset($aLink['element']['attributes']['title'])) ? $sTitle = $aLink['element']['attributes']['title'] : false;
        (true === empty($iWidth) && isset($aLink['element']['attributes']['width'])) ? $iWidth = $aLink['element']['attributes']['width'] : false;
        (true === empty($iHeight) && isset($aLink['element']['attributes']['height'])) ? $iHeight = $aLink['element']['attributes']['height'] : false;

        (false === isset($sTitle) || true === empty($sTitle)) ? $sTitle = $sAlt : false;

        $aInline = parent::inlineImage($aExcerpt);
        $aInline['element']['name'] = 'img';
        $aInline['element']['attributes'] = [];
        $aInline['extent'] = mb_strlen($aExcerpt['text']) + 2;

        (false === empty($sUrl)) ? $aInline['element']['attributes']['src'] = $sUrl : false;
        (false === empty($sAlt)) ? $aInline['element']['attributes']['alt'] = $sAlt : false;
        (false === empty($sTitle)) ? $aInline['element']['attributes']['title'] = $sTitle : false;
        (false === empty($iWidth)) ? $aInline['element']['attributes']['width'] = $iWidth : false;
        (false === empty($iHeight)) ? $aInline['element']['attributes']['height'] = $iHeight : false;

        return $aInline;
    }

    /**
     * modified regex to match image dimensions in reference links
     * @example [1]: http://example.com/foo.png =50x50 "example image"
     * @see https://regexr.com/4rjlp
     * @see https://blog.ueffing.net/post/2020/01/01/parsedown-image-size-dimension-attribute/
     * @param string $sLine
     * @return array
     */
    protected function blockReference($sLine = '')
    {
        $bPregMatchLine = (boolean) preg_match(
            ''
            . '/^\[(.+?)\]:[ ]*<?(\S+?)>?'
            . '('
            .'?:'
            .'[\s]+[=]+[(0-9)]+[x]+[(0-9)]+[\s]'    # this (with image dimensions. e.g. =50x50)
            .'+["\'(](.+)["\')]'
            .'|'                                    # or
            .'[\s]+["\'(](.+)["\')]'                # that (without; standard)
            .')'
            . '?[ ]*$/',
            $sLine['text'],
            $aMatchLine
        );

        // skip empty ones
        $aMatchLine = array_values(array_filter($aMatchLine));

        if (true === $bPregMatchLine)
        {
            $iId = strtolower($aMatchLine[1]);
            $aData = array(
                'url' => $aMatchLine[2],
                'title' => null,
            );

            (isset($aMatchLine[3])) ? $aData['title'] = $aMatchLine[3] : false;

            IMAGE_DIMSENSION: {

                $sPatternImageDimension = '/[=]+[(0-9)]+[x]+[(0-9)]*/'; # e.g. =50x50
                $bPregMatchImageDimension = (boolean) preg_match(
                    $sPatternImageDimension,
                    $aMatchLine[0],
                    $aMatchImageDimension
                );

                if (true === $bPregMatchImageDimension)
                {
                    $sMatchImageDimension = current($aMatchImageDimension);
                    $mStrChr = mb_strrchr($sMatchImageDimension, '=');
                    list($aData['width'], $aData['height']) = (false !== $mStrChr) ? array_map('intval', explode('x', mb_substr($mStrChr, 1))) : array('', '');
                }
            }

            $this->DefinitionData['Reference'][$iId] = $aData;
            $aBlock = array('hidden' => true);

            return $aBlock;
        }
    }

    /**
     * @param array $aExcerpt
     * @return array|void
     */
    protected function inlineLink($aExcerpt)
    {
        $iExtent = 0;
        $sText = $aExcerpt['text'];
        $aElement = array(
            'name' => 'a',
            'handler' => 'line',
            'nonNestables' => array('Url', 'Link'),
            'text' => null,
            'attributes' => array(
                'href' => null,
                'title' => null,
            ),
        );

        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $sText, $aMatch))
        {
            $aElement['text'] = $aMatch[1];
            $iExtent += strlen($aMatch[0]);
            $sText = substr($sText, $iExtent);
        }
        else
        {
            return;
        }

        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $sText, $aMatch))
        {
            $aElement['attributes']['href'] = $aMatch[1];
            (isset($aMatch[2])) ? $aElement['attributes']['title'] = substr($aMatch[2], 1, - 1) : false;
            $iExtent += strlen($aMatch[0]);
        }
        else
        {
            if (preg_match('/^\s*\[(.*?)\]/', $sText, $aMatch))
            {
                $sDefinition = strlen($aMatch[1]) ? $aMatch[1] : $aElement['text'];
                $sDefinition = strtolower($sDefinition);
                $iExtent += strlen($aMatch[0]);
            }
            else
            {
                $sDefinition = strtolower($aElement['text']);
            }

            if ( ! isset($this->DefinitionData['Reference'][$sDefinition]))
            {
                return;
            }

            $aDefinition = $this->DefinitionData['Reference'][$sDefinition];

            $aElement['attributes']['href'] = $aDefinition['url'];
            $aElement['attributes']['title'] = $aDefinition['title'];

            (isset($aDefinition['width'])) ? $aElement['attributes']['width'] = $aDefinition['width'] : false;
            (isset($aDefinition['height'])) ? $aElement['attributes']['height'] = $aDefinition['height'] : false;
        }

        $aReturn = array(
            'extent' => $iExtent,
            'element' => $aElement,
        );

        return $aReturn;
    }

    /**
     * @param string $sText
     * @return mixed|string
     */
    protected static function getRelevantMarkdown($sText = '')
    {
        $aExplode = preg_split("@!\[@", $sText, NULL, PREG_SPLIT_NO_EMPTY);
        $sValue = '';

        foreach ($aExplode as $iKey => $sValue)
        {
            $sValue = '![' . strtok($sValue, ')') . ')';
            $sValue = strtok($sValue, ')');
            if ($sValue[1] != '['){continue;} # skip irrelevant
            break;
        }

        // this is what we want
        return $sValue;
    }

    /**
     * @param string $sContent
     * @return array
     */
    protected static function getUrlFromString($sContent = '')
    {
        $sPattern = "/(?:(?:\/|https?|ftp))[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i";

        preg_match_all(
            $sPattern,
            $sContent,
            $aMatch,
            PREG_PATTERN_ORDER
        );

        return current($aMatch);
    }
}

Links

This website uses Cookies to provide you with the best possible service. Please see our Privacy Policy for more information. Click the check box below to accept cookies. Then confirm with a click on "Save".