Plugin Directory

Changeset 1153658


Ignore:
Timestamp:
05/05/2015 11:13:01 AM (11 years ago)
Author:
cadeyrn
Message:

version 0.3

Location:
wp-parsedown/trunk
Files:
1 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • wp-parsedown/trunk/lib/parsedown-extra/ParsedownExtra.php

    r953966 r1153658  
    1616class ParsedownExtra extends Parsedown
    1717{
    18     #
     18    # ~
     19
     20    const version = '0.7.0';
     21
    1922    # ~
    2023
    2124    function __construct()
    2225    {
     26        if (parent::version < '1.5.0')
     27        {
     28            throw new Exception('ParsedownExtra requires a later version of Parsedown');
     29        }
     30
    2331        $this->BlockTypes[':'] []= 'DefinitionList';
    24 
    25         $this->DefinitionTypes['*'] []= 'Abbreviation';
     32        $this->BlockTypes['*'] []= 'Abbreviation';
    2633
    2734        # identify footnote definitions before reference definitions
    28         array_unshift($this->DefinitionTypes['['], 'Footnote');
     35        array_unshift($this->BlockTypes['['], 'Footnote');
    2936
    3037        # identify footnote markers before before links
    31         array_unshift($this->SpanTypes['['], 'FootnoteMarker');
     38        array_unshift($this->InlineTypes['['], 'FootnoteMarker');
    3239    }
    3340
     
    4552        # add footnotes
    4653
    47         if (isset($this->Definitions['Footnote']))
     54        if (isset($this->DefinitionData['Footnote']))
    4855        {
    4956            $Element = $this->buildFootnoteElement();
     
    6067
    6168    #
    62     # Atx
    63 
    64     protected function identifyAtx($Line)
    65     {
    66         $Block = parent::identifyAtx($Line);
    67 
    68         if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
    69         {
    70             $attributeString = $matches[1][0];
    71 
    72             $Block['element']['attributes'] = $this->parseAttributes($attributeString);
    73 
    74             $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
    75         }
     69    # Abbreviation
     70
     71    protected function blockAbbreviation($Line)
     72    {
     73        if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
     74        {
     75            $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
     76
     77            $Block = array(
     78                'hidden' => true,
     79            );
     80
     81            return $Block;
     82        }
     83    }
     84
     85    #
     86    # Footnote
     87
     88    protected function blockFootnote($Line)
     89    {
     90        if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
     91        {
     92            $Block = array(
     93                'label' => $matches[1],
     94                'text' => $matches[2],
     95                'hidden' => true,
     96            );
     97
     98            return $Block;
     99        }
     100    }
     101
     102    protected function blockFootnoteContinue($Line, $Block)
     103    {
     104        if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
     105        {
     106            return;
     107        }
     108
     109        if (isset($Block['interrupted']))
     110        {
     111            if ($Line['indent'] >= 4)
     112            {
     113                $Block['text'] .= "\n\n" . $Line['text'];
     114
     115                return $Block;
     116            }
     117        }
     118        else
     119        {
     120            $Block['text'] .= "\n" . $Line['text'];
     121
     122            return $Block;
     123        }
     124    }
     125
     126    protected function blockFootnoteComplete($Block)
     127    {
     128        $this->DefinitionData['Footnote'][$Block['label']] = array(
     129            'text' => $Block['text'],
     130            'count' => null,
     131            'number' => null,
     132        );
    76133
    77134        return $Block;
     
    81138    # Definition List
    82139
    83     protected function identifyDefinitionList($Line, $Block)
    84     {
    85         if (isset($Block['type']))
     140    protected function blockDefinitionList($Line, $Block)
     141    {
     142        if ( ! isset($Block) or isset($Block['type']))
    86143        {
    87144            return;
     
    105162        }
    106163
    107         $Element['text'] []= array(
    108             'name' => 'dd',
    109             'handler' => 'line',
    110             'text' => ltrim($Line['text'], ' :'),
    111         );
    112 
    113164        $Block['element'] = $Element;
    114165
     166        $Block = $this->addDdElement($Line, $Block);
     167
    115168        return $Block;
    116169    }
    117170
    118     protected function addToDefinitionList($Line, array $Block)
     171    protected function blockDefinitionListContinue($Line, array $Block)
    119172    {
    120173        if ($Line['text'][0] === ':')
    121174        {
    122             $Block['element']['text'] []= array(
    123                 'name' => 'dd',
    124                 'handler' => 'line',
    125                 'text' => ltrim($Line['text'], ' :'),
    126             );
     175            $Block = $this->addDdElement($Line, $Block);
    127176
    128177            return $Block;
    129178        }
    130 
    131         if ( ! isset($Block['interrupted']))
    132         {
    133             $Element = array_pop($Block['element']['text']);
    134 
    135             $Element['text'] .= "\n" . chop($Line['text']);
    136 
    137             $Block['element']['text'] []= $Element;
     179        else
     180        {
     181            if (isset($Block['interrupted']) and $Line['indent'] === 0)
     182            {
     183                return;
     184            }
     185
     186            if (isset($Block['interrupted']))
     187            {
     188                $Block['dd']['handler'] = 'text';
     189                $Block['dd']['text'] .= "\n\n";
     190
     191                unset($Block['interrupted']);
     192            }
     193
     194            $text = substr($Line['body'], min($Line['indent'], 4));
     195
     196            $Block['dd']['text'] .= "\n" . $text;
    138197
    139198            return $Block;
     
    142201
    143202    #
     203    # Header
     204
     205    protected function blockHeader($Line)
     206    {
     207        $Block = parent::blockHeader($Line);
     208
     209        if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
     210        {
     211            $attributeString = $matches[1][0];
     212
     213            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
     214
     215            $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
     216        }
     217
     218        return $Block;
     219    }
     220
     221    #
     222    # Markup
     223
     224    protected function blockMarkupComplete($Block)
     225    {
     226        if ( ! isset($Block['void']))
     227        {
     228            $Block['markup'] = $this->processTag($Block['markup']);
     229        }
     230
     231        return $Block;
     232    }
     233
     234    #
    144235    # Setext
    145236
    146     protected function identifySetext($Line, array $Block = null)
    147     {
    148         $Block = parent::identifySetext($Line, $Block);
    149 
    150         if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
     237    protected function blockSetextHeader($Line, array $Block = null)
     238    {
     239        $Block = parent::blockSetextHeader($Line, $Block);
     240
     241        if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
    151242        {
    152243            $attributeString = $matches[1][0];
    153244
    154             $Block['element']['attributes'] = $this->parseAttributes($attributeString);
     245            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
    155246
    156247            $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
     
    161252
    162253    #
    163     # Definitions
    164     #
    165 
    166     #
    167     # Abbreviation
    168 
    169     protected function identifyAbbreviation($Line)
    170     {
    171         if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
    172         {
    173             $Abbreviation = array(
    174                 'id' => $matches[1],
    175                 'data' => $matches[2],
    176             );
    177 
    178             return $Abbreviation;
    179         }
    180     }
    181 
    182     #
    183     # Footnote
    184 
    185     protected function identifyFootnote($Line)
    186     {
    187         if (preg_match('/^\[\^(.+?)\]:[ ]?(.+)$/', $Line['text'], $matches))
    188         {
    189             $Footnote = array(
    190                 'id' => $matches[1],
    191                 'data' => array(
    192                     'text' => $matches[2],
    193                     'count' => null,
    194                     'number' => null,
    195                 ),
    196             );
    197 
    198             return $Footnote;
    199         }
    200     }
    201 
    202     #
    203     # Spans
     254    # Inline Elements
    204255    #
    205256
     
    207258    # Footnote Marker
    208259
    209     protected function identifyFootnoteMarker($Excerpt)
     260    protected function inlineFootnoteMarker($Excerpt)
    210261    {
    211262        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
     
    213264            $name = $matches[1];
    214265
    215             if ( ! isset($this->Definitions['Footnote'][$name]))
     266            if ( ! isset($this->DefinitionData['Footnote'][$name]))
    216267            {
    217268                return;
    218269            }
    219270
    220             $this->Definitions['Footnote'][$name]['count'] ++;
    221 
    222             if ( ! isset($this->Definitions['Footnote'][$name]['number']))
    223             {
    224                 $this->Definitions['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
     271            $this->DefinitionData['Footnote'][$name]['count'] ++;
     272
     273            if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
     274            {
     275                $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
    225276            }
    226277
    227278            $Element = array(
    228279                'name' => 'sup',
    229                 'attributes' => array('id' => 'fnref'.$this->Definitions['Footnote'][$name]['count'].':'.$name),
     280                'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
    230281                'handler' => 'element',
    231282                'text' => array(
    232283                    'name' => 'a',
    233284                    'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
    234                     'text' => $this->Definitions['Footnote'][$name]['number'],
     285                    'text' => $this->DefinitionData['Footnote'][$name]['number'],
    235286                ),
    236287            );
     
    248299    # Link
    249300
    250     protected function identifyLink($Excerpt)
    251     {
    252         $Span = parent::identifyLink($Excerpt);
    253 
    254         $remainder = substr($Excerpt['text'], $Span['extent']);
    255 
    256         if (preg_match('/^[ ]*'.$this->attributesPattern.'/', $remainder, $matches))
    257         {
    258             $Span['element']['attributes'] += $this->parseAttributes($matches[1]);
    259 
    260             $Span['extent'] += strlen($matches[0]);
    261         }
    262 
    263         return $Span;
    264     }
    265 
    266     #
    267     # ~
    268 
    269     protected function readPlainText($text)
    270     {
    271         $text = parent::readPlainText($text);
    272 
    273         if (isset($this->Definitions['Abbreviation']))
    274         {
    275             foreach ($this->Definitions['Abbreviation'] as $abbreviation => $phrase)
    276             {
    277                 $text = str_replace($abbreviation, '<abbr title="'.$phrase.'">'.$abbreviation.'</abbr>', $text);
     301    protected function inlineLink($Excerpt)
     302    {
     303        $Link = parent::inlineLink($Excerpt);
     304
     305        $remainder = substr($Excerpt['text'], $Link['extent']);
     306
     307        if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
     308        {
     309            $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
     310
     311            $Link['extent'] += strlen($matches[0]);
     312        }
     313
     314        return $Link;
     315    }
     316
     317    #
     318    # ~
     319    #
     320
     321    protected function unmarkedText($text)
     322    {
     323        $text = parent::unmarkedText($text);
     324
     325        if (isset($this->DefinitionData['Abbreviation']))
     326        {
     327            foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
     328            {
     329                $pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
     330
     331                $text = preg_replace($pattern, '<abbr title="'.$meaning.'">'.$abbreviation.'</abbr>', $text);
    278332            }
    279333        }
     
    283337
    284338    #
    285     # ~
    286     #
     339    # Util Methods
     340    #
     341
     342    protected function addDdElement(array $Line, array $Block)
     343    {
     344        $text = substr($Line['text'], 1);
     345        $text = trim($text);
     346
     347        unset($Block['dd']);
     348
     349        $Block['dd'] = array(
     350            'name' => 'dd',
     351            'handler' => 'line',
     352            'text' => $text,
     353        );
     354
     355        if (isset($Block['interrupted']))
     356        {
     357            $Block['dd']['handler'] = 'text';
     358
     359            unset($Block['interrupted']);
     360        }
     361
     362        $Block['element']['text'] []= & $Block['dd'];
     363
     364        return $Block;
     365    }
    287366
    288367    protected function buildFootnoteElement()
     
    304383        );
    305384
    306         usort($this->Definitions['Footnote'], function($A, $B) {
    307             return $A['number'] - $B['number'];
    308         });
    309 
    310         foreach ($this->Definitions['Footnote'] as $name => $Data)
    311         {
    312             if ( ! isset($Data['number']))
     385        uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
     386
     387        foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
     388        {
     389            if ( ! isset($DefinitionData['number']))
    313390            {
    314391                continue;
    315392            }
    316393
    317             $text = $Data['text'];
    318 
    319             foreach (range(1, $Data['count']) as $number)
    320             {
    321                 $text .= '&#160;<a href="#fnref'.$number.':'.$name.'" rev="footnote" class="footnote-backref">&#8617;</a>';
     394            $text = $DefinitionData['text'];
     395
     396            $text = parent::text($text);
     397
     398            $numbers = range(1, $DefinitionData['count']);
     399
     400            $backLinksMarkup = '';
     401
     402            foreach ($numbers as $number)
     403            {
     404                $backLinksMarkup .= ' <a href="#fnref'.$number.':'.$definitionId.'" rev="footnote" class="footnote-backref">&#8617;</a>';
     405            }
     406
     407            $backLinksMarkup = substr($backLinksMarkup, 1);
     408
     409            if (substr($text, - 4) === '</p>')
     410            {
     411                $backLinksMarkup = '&#160;'.$backLinksMarkup;
     412
     413                $text = substr_replace($text, $backLinksMarkup.'</p>', - 4);
     414            }
     415            else
     416            {
     417                $text .= "\n".'<p>'.$backLinksMarkup.'</p>';
    322418            }
    323419
    324420            $Element['text'][1]['text'] []= array(
    325421                'name' => 'li',
    326                 'attributes' => array('id' => 'fn:'.$name),
    327                 'handler' => 'elements',
    328                 'text' => array(
    329                     array(
    330                         'name' => 'p',
    331                         'text' => $text,
    332                     ),
    333                 ),
     422                'attributes' => array('id' => 'fn:'.$definitionId),
     423                'text' => "\n".$text."\n",
    334424            );
    335425        }
     
    338428    }
    339429
    340     #
    341     # Private
    342     #
    343 
    344     private function parseAttributes($attributeString)
     430    # ~
     431
     432    protected function parseAttributeData($attributeString)
    345433    {
    346434        $Data = array();
     
    368456    }
    369457
    370     private $attributesPattern = '{((?:[#.][-\w]+[ ]*)+)}';
     458    # ~
     459
     460    protected function processTag($elementMarkup) # recursive
     461    {
     462        # http://stackoverflow.com/q/1148928/200145
     463        libxml_use_internal_errors(true);
     464
     465        $DOMDocument = new DOMDocument;
     466
     467        # http://stackoverflow.com/q/11309194/200145
     468        $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
     469
     470        # http://stackoverflow.com/q/4879946/200145
     471        $DOMDocument->loadHTML($elementMarkup);
     472        $DOMDocument->removeChild($DOMDocument->doctype);
     473        $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
     474
     475        $elementText = '';
     476
     477        if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
     478        {
     479            foreach ($DOMDocument->documentElement->childNodes as $Node)
     480            {
     481                $elementText .= $DOMDocument->saveHTML($Node);
     482            }
     483
     484            $DOMDocument->documentElement->removeAttribute('markdown');
     485
     486            $elementText = "\n".$this->text($elementText)."\n";
     487        }
     488        else
     489        {
     490            foreach ($DOMDocument->documentElement->childNodes as $Node)
     491            {
     492                $nodeMarkup = $DOMDocument->saveHTML($Node);
     493
     494                if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
     495                {
     496                    $elementText .= $this->processTag($nodeMarkup);
     497                }
     498                else
     499                {
     500                    $elementText .= $nodeMarkup;
     501                }
     502            }
     503        }
     504
     505        # because we don't want for markup to get encoded
     506        $DOMDocument->documentElement->nodeValue = 'placeholder';
     507
     508        $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
     509        $markup = str_replace('placeholder', $elementText, $markup);
     510
     511        return $markup;
     512    }
     513
     514    # ~
     515
     516    protected function sortFootnotes($A, $B) # callback
     517    {
     518        return $A['number'] - $B['number'];
     519    }
     520
     521    #
     522    # Fields
     523    #
     524
     525    protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
    371526}
  • wp-parsedown/trunk/lib/parsedown-extra/README.md

    r953966 r1153658  
    11## Parsedown Extra
    22
    3 An extension of [Parsedown](http://parsedown.org) that adds support for [Markdown Extra](http://en.wikipedia.org/wiki/Markdown_Extra).
     3[![Build Status](https://img.shields.io/travis/erusev/parsedown-extra/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown-extra)
    44
    5 [[ demo ]](http://parsedown.org/demo?extra=1)
     5An extension of [Parsedown](http://parsedown.org) that adds support for [Markdown Extra](https://michelf.ca/projects/php-markdown/extra/).
     6
     7[See Demo](http://parsedown.org/extra/)
    68
    79### Installation
     
    1214
    1315``` php
    14 $Instance = new ParsedownExtra();
     16$Extra = new ParsedownExtra();
    1517
    16 echo $Instance->text('Hello _Parsedown Extra_!'); # prints: <p>Hello <em>Parsedown Extra</em>!</p>
     18echo $Extra->text('# Header {.sth}'); # prints: <h1 class="sth">Header</h1>
    1719```
     20
     21### Questions
     22
     23**Who uses Parsedown Extra?**
     24
     25[October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/) and [more](https://www.versioneye.com/php/erusev:parsedown-extra/references).
     26
     27**How can I help?**
     28
     29Use it, star it, share it and in case you feel generous, [donate some money](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).
  • wp-parsedown/trunk/lib/parsedown/Parsedown.php

    r953966 r1153658  
    1616class Parsedown
    1717{
    18     #
    19     # Philosophy
    20 
    21     # Parsedown recognises that the Markdown syntax is optimised for humans so
    22     # it tries to read like one. It goes through text line by line. It looks at
    23     # how lines start to identify blocks. It looks for special characters to
    24     # identify inline elements.
    25 
    26     #
    2718    # ~
    2819
     20    const version = '1.5.3';
     21
     22    # ~
     23
    2924    function text($text)
    3025    {
    3126        # make sure no definitions are set
    32         $this->Definitions = array();
     27        $this->DefinitionData = array();
    3328
    3429        # standardize line breaks
    35         $text = str_replace("\r\n", "\n", $text);
    36         $text = str_replace("\r", "\n", $text);
    37 
    38         # replace tabs with spaces
    39         $text = str_replace("\t", '    ', $text);
     30        $text = str_replace(array("\r\n", "\r"), "\n", $text);
    4031
    4132        # remove surrounding line breaks
     
    5849    #
    5950
    60     private $breaksEnabled;
    61 
    6251    function setBreaksEnabled($breaksEnabled)
    6352    {
     
    6756    }
    6857
     58    protected $breaksEnabled;
     59
     60    function setMarkupEscaped($markupEscaped)
     61    {
     62        $this->markupEscaped = $markupEscaped;
     63
     64        return $this;
     65    }
     66
     67    protected $markupEscaped;
     68
     69    function setUrlsLinked($urlsLinked)
     70    {
     71        $this->urlsLinked = $urlsLinked;
     72
     73        return $this;
     74    }
     75
     76    protected $urlsLinked = true;
     77
    6978    #
    7079    # Lines
     
    7281
    7382    protected $BlockTypes = array(
    74         '#' => array('Atx'),
     83        '#' => array('Header'),
    7584        '*' => array('Rule', 'List'),
    7685        '+' => array('List'),
    77         '-' => array('Setext', 'Table', 'Rule', 'List'),
     86        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
    7887        '0' => array('List'),
    7988        '1' => array('List'),
     
    8897        ':' => array('Table'),
    8998        '<' => array('Comment', 'Markup'),
    90         '=' => array('Setext'),
     99        '=' => array('SetextHeader'),
    91100        '>' => array('Quote'),
     101        '[' => array('Reference'),
    92102        '_' => array('Rule'),
    93103        '`' => array('FencedCode'),
     
    105115
    106116    protected $unmarkedBlockTypes = array(
    107         'CodeBlock',
     117        'Code',
    108118    );
    109119
     
    128138            }
    129139
     140            if (strpos($line, "\t") !== false)
     141            {
     142                $parts = explode("\t", $line);
     143
     144                $line = $parts[0];
     145
     146                unset($parts[0]);
     147
     148                foreach ($parts as $part)
     149                {
     150                    $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
     151
     152                    $line .= str_repeat(' ', $shortage);
     153                    $line .= $part;
     154                }
     155            }
     156
    130157            $indent = 0;
    131158
     
    145172            if (isset($CurrentBlock['incomplete']))
    146173            {
    147                 $Block = $this->{'addTo'.$CurrentBlock['type']}($Line, $CurrentBlock);
     174                $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
    148175
    149176                if (isset($Block))
     
    155182                else
    156183                {
    157                     if (method_exists($this, 'complete'.$CurrentBlock['type']))
     184                    if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
    158185                    {
    159                         $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock);
     186                        $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
    160187                    }
    161188
     
    167194
    168195            $marker = $text[0];
    169 
    170             if (isset($this->DefinitionTypes[$marker]))
    171             {
    172                 foreach ($this->DefinitionTypes[$marker] as $definitionType)
    173                 {
    174                     $Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock);
    175 
    176                     if (isset($Definition))
    177                     {
    178                         $this->Definitions[$definitionType][$Definition['id']] = $Definition['data'];
    179 
    180                         continue 2;
    181                     }
    182                 }
    183             }
    184196
    185197            # ~
     
    200212            foreach ($blockTypes as $blockType)
    201213            {
    202                 $Block = $this->{'identify'.$blockType}($Line, $CurrentBlock);
     214                $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
    203215
    204216                if (isset($Block))
     
    208220                    if ( ! isset($Block['identified']))
    209221                    {
    210                         $Elements []= $CurrentBlock['element'];
     222                        $Blocks []= $CurrentBlock;
    211223
    212224                        $Block['identified'] = true;
    213225                    }
    214226
    215                     if (method_exists($this, 'addTo'.$blockType))
     227                    if (method_exists($this, 'block'.$blockType.'Continue'))
    216228                    {
    217229                        $Block['incomplete'] = true;
     
    232244            else
    233245            {
    234                 $Elements []= $CurrentBlock['element'];
    235 
    236                 $CurrentBlock = $this->buildParagraph($Line);
     246                $Blocks []= $CurrentBlock;
     247
     248                $CurrentBlock = $this->paragraph($Line);
    237249
    238250                $CurrentBlock['identified'] = true;
     
    242254        # ~
    243255
    244         if (isset($CurrentBlock['incomplete']) and method_exists($this, 'complete'.$CurrentBlock['type']))
    245         {
    246             $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock);
     256        if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
     257        {
     258            $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
    247259        }
    248260
    249261        # ~
    250262
    251         $Elements []= $CurrentBlock['element'];
    252 
    253         unset($Elements[0]);
     263        $Blocks []= $CurrentBlock;
     264
     265        unset($Blocks[0]);
    254266
    255267        # ~
    256268
    257         $markup = $this->elements($Elements);
     269        $markup = '';
     270
     271        foreach ($Blocks as $Block)
     272        {
     273            if (isset($Block['hidden']))
     274            {
     275                continue;
     276            }
     277
     278            $markup .= "\n";
     279            $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
     280        }
     281
     282        $markup .= "\n";
    258283
    259284        # ~
     
    263288
    264289    #
    265     # Atx
    266 
    267     protected function identifyAtx($Line)
    268     {
    269         if (isset($Line['text'][1]))
    270         {
    271             $level = 1;
    272 
    273             while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
    274             {
    275                 $level ++;
    276             }
    277 
    278             $text = trim($Line['text'], '# ');
    279 
    280             $Block = array(
    281                 'element' => array(
    282                     'name' => 'h'.$level,
    283                     'text' => $text,
    284                     'handler' => 'line',
    285                 ),
    286             );
    287 
    288             return $Block;
    289         }
    290     }
    291 
    292     #
    293290    # Code
    294291
    295     protected function identifyCodeBlock($Line)
    296     {
     292    protected function blockCode($Line, $Block = null)
     293    {
     294        if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
     295        {
     296            return;
     297        }
     298
    297299        if ($Line['indent'] >= 4)
    298300        {
     
    314316    }
    315317
    316     protected function addToCodeBlock($Line, $Block)
     318    protected function blockCodeContinue($Line, $Block)
    317319    {
    318320        if ($Line['indent'] >= 4)
     
    335337    }
    336338
    337     protected function completeCodeBlock($Block)
     339    protected function blockCodeComplete($Block)
    338340    {
    339341        $text = $Block['element']['text']['text'];
     
    349351    # Comment
    350352
    351     protected function identifyComment($Line)
    352     {
     353    protected function blockComment($Line)
     354    {
     355        if ($this->markupEscaped)
     356        {
     357            return;
     358        }
     359
    353360        if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
    354361        {
    355362            $Block = array(
    356                 'element' => $Line['body'],
     363                'markup' => $Line['body'],
    357364            );
    358365
     
    366373    }
    367374
    368     protected function addToComment($Line, array $Block)
     375    protected function blockCommentContinue($Line, array $Block)
    369376    {
    370377        if (isset($Block['closed']))
     
    373380        }
    374381
    375         $Block['element'] .= "\n" . $Line['body'];
     382        $Block['markup'] .= "\n" . $Line['body'];
    376383
    377384        if (preg_match('/-->$/', $Line['text']))
     
    386393    # Fenced Code
    387394
    388     protected function identifyFencedCode($Line)
     395    protected function blockFencedCode($Line)
    389396    {
    390397        if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
     
    417424    }
    418425
    419     protected function addToFencedCode($Line, $Block)
     426    protected function blockFencedCodeContinue($Line, $Block)
    420427    {
    421428        if (isset($Block['complete']))
     
    445452    }
    446453
    447     protected function completeFencedCode($Block)
     454    protected function blockFencedCodeComplete($Block)
    448455    {
    449456        $text = $Block['element']['text']['text'];
     
    457464
    458465    #
     466    # Header
     467
     468    protected function blockHeader($Line)
     469    {
     470        if (isset($Line['text'][1]))
     471        {
     472            $level = 1;
     473
     474            while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
     475            {
     476                $level ++;
     477            }
     478
     479            if ($level > 6)
     480            {
     481                return;
     482            }
     483
     484            $text = trim($Line['text'], '# ');
     485
     486            $Block = array(
     487                'element' => array(
     488                    'name' => 'h' . min(6, $level),
     489                    'text' => $text,
     490                    'handler' => 'line',
     491                ),
     492            );
     493
     494            return $Block;
     495        }
     496    }
     497
     498    #
    459499    # List
    460500
    461     protected function identifyList($Line)
     501    protected function blockList($Line)
    462502    {
    463503        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
     
    488528    }
    489529
    490     protected function addToList($Line, array $Block)
    491     {
    492         if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'[ ]+(.*)/', $Line['text'], $matches))
     530    protected function blockListContinue($Line, array $Block)
     531    {
     532        if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
    493533        {
    494534            if (isset($Block['interrupted']))
     
    500540
    501541            unset($Block['li']);
     542
     543            $text = isset($matches[1]) ? $matches[1] : '';
    502544
    503545            $Block['li'] = array(
     
    505547                'handler' => 'li',
    506548                'text' => array(
    507                     $matches[1],
     549                    $text,
    508550                ),
    509551            );
     
    514556        }
    515557
     558        if ($Line['text'][0] === '[' and $this->blockReference($Line))
     559        {
     560            return $Block;
     561        }
     562
    516563        if ( ! isset($Block['interrupted']))
    517564        {
     
    540587    # Quote
    541588
    542     protected function identifyQuote($Line)
     589    protected function blockQuote($Line)
    543590    {
    544591        if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
     
    556603    }
    557604
    558     protected function addToQuote($Line, array $Block)
     605    protected function blockQuoteContinue($Line, array $Block)
    559606    {
    560607        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
     
    583630    # Rule
    584631
    585     protected function identifyRule($Line)
    586     {
    587         if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text']))
     632    protected function blockRule($Line)
     633    {
     634        if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
    588635        {
    589636            $Block = array(
     
    600647    # Setext
    601648
    602     protected function identifySetext($Line, array $Block = null)
     649    protected function blockSetextHeader($Line, array $Block = null)
    603650    {
    604651        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
     
    618665    # Markup
    619666
    620     protected function identifyMarkup($Line)
    621     {
    622         if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>\/]*)?(\/?)[ ]*>/', $Line['text'], $matches))
     667    protected function blockMarkup($Line)
     668    {
     669        if ($this->markupEscaped)
     670        {
     671            return;
     672        }
     673
     674        if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
    623675        {
    624676            if (in_array($matches[1], $this->textLevelElements))
     
    628680
    629681            $Block = array(
    630                 'element' => $Line['body'],
    631             );
    632 
    633             if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text']))
     682                'name' => $matches[1],
     683                'depth' => 0,
     684                'markup' => $Line['text'],
     685            );
     686
     687            $length = strlen($matches[0]);
     688
     689            $remainder = substr($Line['text'], $length);
     690
     691            if (trim($remainder) === '')
     692            {
     693                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
     694                {
     695                    $Block['closed'] = true;
     696
     697                    $Block['void'] = true;
     698                }
     699            }
     700            else
     701            {
     702                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
     703                {
     704                    return;
     705                }
     706
     707                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
     708                {
     709                    $Block['closed'] = true;
     710                }
     711            }
     712
     713            return $Block;
     714        }
     715    }
     716
     717    protected function blockMarkupContinue($Line, array $Block)
     718    {
     719        if (isset($Block['closed']))
     720        {
     721            return;
     722        }
     723
     724        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
     725        {
     726            $Block['depth'] ++;
     727        }
     728
     729        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
     730        {
     731            if ($Block['depth'] > 0)
     732            {
     733                $Block['depth'] --;
     734            }
     735            else
    634736            {
    635737                $Block['closed'] = true;
    636738            }
    637             else
    638             {
    639                 $Block['depth'] = 0;
    640                 $Block['name'] = $matches[1];
    641             }
    642 
    643             return $Block;
    644         }
    645     }
    646 
    647     protected function addToMarkup($Line, array $Block)
    648     {
    649         if (isset($Block['closed']))
    650         {
    651             return;
    652         }
    653 
    654         if (preg_match('/<'.$Block['name'].'([ ][^\/]+)?>/', $Line['text'])) # opening tag
    655         {
    656             $Block['depth'] ++;
    657         }
    658 
    659         if (stripos($Line['text'], '</'.$Block['name'].'>') !== false) # closing tag
    660         {
    661             if ($Block['depth'] > 0)
    662             {
    663                 $Block['depth'] --;
    664             }
    665             else
    666             {
    667                 $Block['closed'] = true;
    668             }
    669         }
    670 
    671         $Block['element'] .= "\n".$Line['body'];
     739        }
     740
     741        if (isset($Block['interrupted']))
     742        {
     743            $Block['markup'] .= "\n";
     744
     745            unset($Block['interrupted']);
     746        }
     747
     748        $Block['markup'] .= "\n".$Line['body'];
    672749
    673750        return $Block;
     
    675752
    676753    #
     754    # Reference
     755
     756    protected function blockReference($Line)
     757    {
     758        if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
     759        {
     760            $id = strtolower($matches[1]);
     761
     762            $Data = array(
     763                'url' => $matches[2],
     764                'title' => null,
     765            );
     766
     767            if (isset($matches[3]))
     768            {
     769                $Data['title'] = $matches[3];
     770            }
     771
     772            $this->DefinitionData['Reference'][$id] = $Data;
     773
     774            $Block = array(
     775                'hidden' => true,
     776            );
     777
     778            return $Block;
     779        }
     780    }
     781
     782    #
    677783    # Table
    678784
    679     protected function identifyTable($Line, array $Block = null)
     785    protected function blockTable($Line, array $Block = null)
    680786    {
    681787        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
     
    711817                }
    712818
    713                 if (substr($dividerCell, -1) === ':')
     819                if (substr($dividerCell, - 1) === ':')
    714820                {
    715821                    $alignment = $alignment === 'left' ? 'center' : 'right';
     
    745851
    746852                    $HeaderElement['attributes'] = array(
    747                         'align' => $alignment,
     853                        'style' => 'text-align: '.$alignment.';',
    748854                    );
    749855                }
     
    784890    }
    785891
    786     protected function addToTable($Line, array $Block)
    787     {
     892    protected function blockTableContinue($Line, array $Block)
     893    {
     894        if (isset($Block['interrupted']))
     895        {
     896            return;
     897        }
     898
    788899        if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
    789900        {
     
    795906            $row = trim($row, '|');
    796907
    797             $cells = explode('|', $row);
    798 
    799             foreach ($cells as $index => $cell)
     908            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
     909
     910            foreach ($matches[0] as $index => $cell)
    800911            {
    801912                $cell = trim($cell);
     
    810921                {
    811922                    $Element['attributes'] = array(
    812                         'align' => $Block['alignments'][$index],
     923                        'style' => 'text-align: '.$Block['alignments'][$index].';',
    813924                    );
    814925                }
     
    830941
    831942    #
    832     # Definitions
    833     #
    834 
    835     protected function identifyReference($Line)
    836     {
    837         if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
    838         {
    839             $Definition = array(
    840                 'id' => strtolower($matches[1]),
    841                 'data' => array(
    842                     'url' => $matches[2],
    843                 ),
    844             );
    845 
    846             if (isset($matches[3]))
    847             {
    848                 $Definition['data']['title'] = $matches[3];
    849             }
    850 
    851             return $Definition;
    852         }
    853     }
    854 
    855     #
    856943    # ~
    857944    #
    858945
    859     protected function buildParagraph($Line)
     946    protected function paragraph($Line)
    860947    {
    861948        $Block = array(
     
    871958
    872959    #
    873     # ~
    874     #
    875 
    876     protected function element(array $Element)
    877     {
    878         $markup = '<'.$Element['name'];
    879 
    880         if (isset($Element['attributes']))
    881         {
    882             foreach ($Element['attributes'] as $name => $value)
    883             {
    884                 $markup .= ' '.$name.'="'.$value.'"';
    885             }
    886         }
    887 
    888         if (isset($Element['text']))
    889         {
    890             $markup .= '>';
    891 
    892             if (isset($Element['handler']))
    893             {
    894                 $markup .= $this->$Element['handler']($Element['text']);
    895             }
    896             else
    897             {
    898                 $markup .= $Element['text'];
    899             }
    900 
    901             $markup .= '</'.$Element['name'].'>';
    902         }
    903         else
    904         {
    905             $markup .= ' />';
    906         }
    907 
    908         return $markup;
    909     }
    910 
    911     protected function elements(array $Elements)
    912     {
    913         $markup = '';
    914 
    915         foreach ($Elements as $Element)
    916         {
    917             if ($Element === null)
    918             {
    919                 continue;
    920             }
    921 
    922             $markup .= "\n";
    923 
    924             if (is_string($Element)) # because of Markup
    925             {
    926                 $markup .= $Element;
    927 
    928                 continue;
    929             }
    930 
    931             $markup .= $this->element($Element);
    932         }
    933 
    934         $markup .= "\n";
    935 
    936         return $markup;
    937     }
    938 
    939     #
    940     # Spans
    941     #
    942 
    943     protected $SpanTypes = array(
    944         '!' => array('Link'), # ?
    945         '&' => array('Ampersand'),
     960    # Inline Elements
     961    #
     962
     963    protected $InlineTypes = array(
     964        '"' => array('SpecialCharacter'),
     965        '!' => array('Image'),
     966        '&' => array('SpecialCharacter'),
    946967        '*' => array('Emphasis'),
    947         '/' => array('Url'),
    948         '<' => array('UrlTag', 'EmailTag', 'Tag', 'LessThan'),
     968        ':' => array('Url'),
     969        '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
     970        '>' => array('SpecialCharacter'),
    949971        '[' => array('Link'),
    950972        '_' => array('Emphasis'),
    951         '`' => array('InlineCode'),
     973        '`' => array('Code'),
    952974        '~' => array('Strikethrough'),
    953975        '\\' => array('EscapeSequence'),
     
    956978    # ~
    957979
    958     protected $spanMarkerList = '*_!&[</`~\\';
     980    protected $inlineMarkerList = '!"*_&[:<>`~\\';
    959981
    960982    #
     
    966988        $markup = '';
    967989
    968         $remainder = $text;
     990        $unexaminedText = $text;
    969991
    970992        $markerPosition = 0;
    971993
    972         while ($excerpt = strpbrk($remainder, $this->spanMarkerList))
     994        while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
    973995        {
    974996            $marker = $excerpt[0];
    975997
    976             $markerPosition += strpos($remainder, $marker);
     998            $markerPosition += strpos($unexaminedText, $marker);
    977999
    9781000            $Excerpt = array('text' => $excerpt, 'context' => $text);
    9791001
    980             foreach ($this->SpanTypes[$marker] as $spanType)
    981             {
    982                 $handler = 'identify'.$spanType;
    983 
    984                 $Span = $this->$handler($Excerpt);
    985 
    986                 if ( ! isset($Span))
     1002            foreach ($this->InlineTypes[$marker] as $inlineType)
     1003            {
     1004                $Inline = $this->{'inline'.$inlineType}($Excerpt);
     1005
     1006                if ( ! isset($Inline))
    9871007                {
    9881008                    continue;
    9891009                }
    9901010
    991                 # The identified span can be ahead of the marker.
    992 
    993                 if (isset($Span['position']) and $Span['position'] > $markerPosition)
     1011                if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker
    9941012                {
    9951013                    continue;
    9961014                }
    9971015
    998                 # Spans that start at the position of their marker don't have to set a position.
    999 
    1000                 if ( ! isset($Span['position']))
    1001                 {
    1002                     $Span['position'] = $markerPosition;
    1003                 }
    1004 
    1005                 $plainText = substr($text, 0, $Span['position']);
    1006 
    1007                 $markup .= $this->readPlainText($plainText);
    1008 
    1009                 $markup .= isset($Span['markup']) ? $Span['markup'] : $this->element($Span['element']);
    1010 
    1011                 $text = substr($text, $Span['position'] + $Span['extent']);
    1012 
    1013                 $remainder = $text;
     1016                if ( ! isset($Inline['position']))
     1017                {
     1018                    $Inline['position'] = $markerPosition;
     1019                }
     1020
     1021                $unmarkedText = substr($text, 0, $Inline['position']);
     1022
     1023                $markup .= $this->unmarkedText($unmarkedText);
     1024
     1025                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
     1026
     1027                $text = substr($text, $Inline['position'] + $Inline['extent']);
     1028
     1029                $unexaminedText = $text;
    10141030
    10151031                $markerPosition = 0;
     
    10181034            }
    10191035
    1020             $remainder = substr($excerpt, 1);
     1036            $unexaminedText = substr($excerpt, 1);
    10211037
    10221038            $markerPosition ++;
    10231039        }
    10241040
    1025         $markup .= $this->readPlainText($text);
     1041        $markup .= $this->unmarkedText($text);
    10261042
    10271043        return $markup;
     
    10321048    #
    10331049
    1034     protected function identifyUrl($Excerpt)
    1035     {
    1036         if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '/')
    1037         {
    1038             return;
    1039         }
    1040 
    1041         if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
    1042         {
    1043             $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[0][0]);
     1050    protected function inlineCode($Excerpt)
     1051    {
     1052        $marker = $Excerpt['text'][0];
     1053
     1054        if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
     1055        {
     1056            $text = $matches[2];
     1057            $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
     1058            $text = preg_replace("/[ ]*\n/", ' ', $text);
    10441059
    10451060            return array(
    1046                 'extent' => strlen($matches[0][0]),
    1047                 'position' => $matches[0][1],
     1061                'extent' => strlen($matches[0]),
     1062                'element' => array(
     1063                    'name' => 'code',
     1064                    'text' => $text,
     1065                ),
     1066            );
     1067        }
     1068    }
     1069
     1070    protected function inlineEmailTag($Excerpt)
     1071    {
     1072        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
     1073        {
     1074            $url = $matches[1];
     1075
     1076            if ( ! isset($matches[2]))
     1077            {
     1078                $url = 'mailto:' . $url;
     1079            }
     1080
     1081            return array(
     1082                'extent' => strlen($matches[0]),
    10481083                'element' => array(
    10491084                    'name' => 'a',
    1050                     'text' => $url,
     1085                    'text' => $matches[1],
    10511086                    'attributes' => array(
    10521087                        'href' => $url,
     
    10571092    }
    10581093
    1059     protected function identifyAmpersand($Excerpt)
    1060     {
    1061         if ( ! preg_match('/^&#?\w+;/', $Excerpt['text']))
     1094    protected function inlineEmphasis($Excerpt)
     1095    {
     1096        if ( ! isset($Excerpt['text'][1]))
     1097        {
     1098            return;
     1099        }
     1100
     1101        $marker = $Excerpt['text'][0];
     1102
     1103        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
     1104        {
     1105            $emphasis = 'strong';
     1106        }
     1107        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
     1108        {
     1109            $emphasis = 'em';
     1110        }
     1111        else
     1112        {
     1113            return;
     1114        }
     1115
     1116        return array(
     1117            'extent' => strlen($matches[0]),
     1118            'element' => array(
     1119                'name' => $emphasis,
     1120                'handler' => 'line',
     1121                'text' => $matches[1],
     1122            ),
     1123        );
     1124    }
     1125
     1126    protected function inlineEscapeSequence($Excerpt)
     1127    {
     1128        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
     1129        {
     1130            return array(
     1131                'markup' => $Excerpt['text'][1],
     1132                'extent' => 2,
     1133            );
     1134        }
     1135    }
     1136
     1137    protected function inlineImage($Excerpt)
     1138    {
     1139        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
     1140        {
     1141            return;
     1142        }
     1143
     1144        $Excerpt['text']= substr($Excerpt['text'], 1);
     1145
     1146        $Link = $this->inlineLink($Excerpt);
     1147
     1148        if ($Link === null)
     1149        {
     1150            return;
     1151        }
     1152
     1153        $Inline = array(
     1154            'extent' => $Link['extent'] + 1,
     1155            'element' => array(
     1156                'name' => 'img',
     1157                'attributes' => array(
     1158                    'src' => $Link['element']['attributes']['href'],
     1159                    'alt' => $Link['element']['text'],
     1160                ),
     1161            ),
     1162        );
     1163
     1164        $Inline['element']['attributes'] += $Link['element']['attributes'];
     1165
     1166        unset($Inline['element']['attributes']['href']);
     1167
     1168        return $Inline;
     1169    }
     1170
     1171    protected function inlineLink($Excerpt)
     1172    {
     1173        $Element = array(
     1174            'name' => 'a',
     1175            'handler' => 'line',
     1176            'text' => null,
     1177            'attributes' => array(
     1178                'href' => null,
     1179                'title' => null,
     1180            ),
     1181        );
     1182
     1183        $extent = 0;
     1184
     1185        $remainder = $Excerpt['text'];
     1186
     1187        if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
     1188        {
     1189            $Element['text'] = $matches[1];
     1190
     1191            $extent += strlen($matches[0]);
     1192
     1193            $remainder = substr($remainder, $extent);
     1194        }
     1195        else
     1196        {
     1197            return;
     1198        }
     1199
     1200        if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
     1201        {
     1202            $Element['attributes']['href'] = $matches[1];
     1203
     1204            if (isset($matches[2]))
     1205            {
     1206                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
     1207            }
     1208
     1209            $extent += strlen($matches[0]);
     1210        }
     1211        else
     1212        {
     1213            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
     1214            {
     1215                $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
     1216                $definition = strtolower($definition);
     1217
     1218                $extent += strlen($matches[0]);
     1219            }
     1220            else
     1221            {
     1222                $definition = strtolower($Element['text']);
     1223            }
     1224
     1225            if ( ! isset($this->DefinitionData['Reference'][$definition]))
     1226            {
     1227                return;
     1228            }
     1229
     1230            $Definition = $this->DefinitionData['Reference'][$definition];
     1231
     1232            $Element['attributes']['href'] = $Definition['url'];
     1233            $Element['attributes']['title'] = $Definition['title'];
     1234        }
     1235
     1236        $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
     1237
     1238        return array(
     1239            'extent' => $extent,
     1240            'element' => $Element,
     1241        );
     1242    }
     1243
     1244    protected function inlineMarkup($Excerpt)
     1245    {
     1246        if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
     1247        {
     1248            return;
     1249        }
     1250
     1251        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
     1252        {
     1253            return array(
     1254                'markup' => $matches[0],
     1255                'extent' => strlen($matches[0]),
     1256            );
     1257        }
     1258
     1259        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
     1260        {
     1261            return array(
     1262                'markup' => $matches[0],
     1263                'extent' => strlen($matches[0]),
     1264            );
     1265        }
     1266
     1267        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
     1268        {
     1269            return array(
     1270                'markup' => $matches[0],
     1271                'extent' => strlen($matches[0]),
     1272            );
     1273        }
     1274    }
     1275
     1276    protected function inlineSpecialCharacter($Excerpt)
     1277    {
     1278        if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
    10621279        {
    10631280            return array(
     
    10661283            );
    10671284        }
    1068     }
    1069 
    1070     protected function identifyStrikethrough($Excerpt)
     1285
     1286        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
     1287
     1288        if (isset($SpecialCharacter[$Excerpt['text'][0]]))
     1289        {
     1290            return array(
     1291                'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
     1292                'extent' => 1,
     1293            );
     1294        }
     1295    }
     1296
     1297    protected function inlineStrikethrough($Excerpt)
    10711298    {
    10721299        if ( ! isset($Excerpt['text'][1]))
     
    10881315    }
    10891316
    1090     protected function identifyEscapeSequence($Excerpt)
    1091     {
    1092         if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
    1093         {
    1094             return array(
    1095                 'markup' => $Excerpt['text'][1],
    1096                 'extent' => 2,
    1097             );
    1098         }
    1099     }
    1100 
    1101     protected function identifyLessThan()
    1102     {
    1103         return array(
    1104             'markup' => '&lt;',
    1105             'extent' => 1,
    1106         );
    1107     }
    1108 
    1109     protected function identifyUrlTag($Excerpt)
    1110     {
    1111         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $Excerpt['text'], $matches))
     1317    protected function inlineUrl($Excerpt)
     1318    {
     1319        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
     1320        {
     1321            return;
     1322        }
     1323
     1324        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
     1325        {
     1326            $Inline = array(
     1327                'extent' => strlen($matches[0][0]),
     1328                'position' => $matches[0][1],
     1329                'element' => array(
     1330                    'name' => 'a',
     1331                    'text' => $matches[0][0],
     1332                    'attributes' => array(
     1333                        'href' => $matches[0][0],
     1334                    ),
     1335                ),
     1336            );
     1337
     1338            return $Inline;
     1339        }
     1340    }
     1341
     1342    protected function inlineUrlTag($Excerpt)
     1343    {
     1344        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
    11121345        {
    11131346            $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
     
    11261359    }
    11271360
    1128     protected function identifyEmailTag($Excerpt)
    1129     {
    1130         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\S+?@\S+?)>/', $Excerpt['text'], $matches))
    1131         {
    1132             return array(
    1133                 'extent' => strlen($matches[0]),
    1134                 'element' => array(
    1135                     'name' => 'a',
    1136                     'text' => $matches[1],
    1137                     'attributes' => array(
    1138                         'href' => 'mailto:'.$matches[1],
    1139                     ),
    1140                 ),
    1141             );
    1142         }
    1143     }
    1144 
    1145     protected function identifyTag($Excerpt)
    1146     {
    1147         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<\/?\w.*?>/', $Excerpt['text'], $matches))
    1148         {
    1149             return array(
    1150                 'markup' => $matches[0],
    1151                 'extent' => strlen($matches[0]),
    1152             );
    1153         }
    1154     }
    1155 
    1156     protected function identifyInlineCode($Excerpt)
    1157     {
    1158         $marker = $Excerpt['text'][0];
    1159 
    1160         if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/', $Excerpt['text'], $matches))
    1161         {
    1162             $text = $matches[2];
    1163             $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
    1164 
    1165             return array(
    1166                 'extent' => strlen($matches[0]),
    1167                 'element' => array(
    1168                     'name' => 'code',
    1169                     'text' => $text,
    1170                 ),
    1171             );
    1172         }
    1173     }
    1174 
    1175     protected function identifyLink($Excerpt)
    1176     {
    1177         $extent = $Excerpt['text'][0] === '!' ? 1 : 0;
    1178 
    1179         if (strpos($Excerpt['text'], ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $Excerpt['text'], $matches))
    1180         {
    1181             $Link = array('text' => $matches[1], 'label' => strtolower($matches[1]));
    1182 
    1183             $extent += strlen($matches[0]);
    1184 
    1185             $substring = substr($Excerpt['text'], $extent);
    1186 
    1187             if (preg_match('/^\s*\[([^][]+)\]/', $substring, $matches))
    1188             {
    1189                 $Link['label'] = strtolower($matches[1]);
    1190 
    1191                 if (isset($this->Definitions['Reference'][$Link['label']]))
    1192                 {
    1193                     $Link += $this->Definitions['Reference'][$Link['label']];
    1194 
    1195                     $extent += strlen($matches[0]);
    1196                 }
    1197                 else
    1198                 {
    1199                     return;
    1200                 }
    1201             }
    1202             elseif (isset($this->Definitions['Reference'][$Link['label']]))
    1203             {
    1204                 $Link += $this->Definitions['Reference'][$Link['label']];
    1205 
    1206                 if (preg_match('/^[ ]*\[\]/', $substring, $matches))
    1207                 {
    1208                     $extent += strlen($matches[0]);
    1209                 }
    1210             }
    1211             elseif (preg_match('/^\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $substring, $matches))
    1212             {
    1213                 $Link['url'] = $matches[1];
    1214 
    1215                 if (isset($matches[2]))
    1216                 {
    1217                     $Link['title'] = $matches[2];
    1218                 }
    1219 
    1220                 $extent += strlen($matches[0]);
     1361    # ~
     1362
     1363    protected function unmarkedText($text)
     1364    {
     1365        if ($this->breaksEnabled)
     1366        {
     1367            $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
     1368        }
     1369        else
     1370        {
     1371            $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
     1372            $text = str_replace(" \n", "\n", $text);
     1373        }
     1374
     1375        return $text;
     1376    }
     1377
     1378    #
     1379    # Handlers
     1380    #
     1381
     1382    protected function element(array $Element)
     1383    {
     1384        $markup = '<'.$Element['name'];
     1385
     1386        if (isset($Element['attributes']))
     1387        {
     1388            foreach ($Element['attributes'] as $name => $value)
     1389            {
     1390                if ($value === null)
     1391                {
     1392                    continue;
     1393                }
     1394
     1395                $markup .= ' '.$name.'="'.$value.'"';
     1396            }
     1397        }
     1398
     1399        if (isset($Element['text']))
     1400        {
     1401            $markup .= '>';
     1402
     1403            if (isset($Element['handler']))
     1404            {
     1405                $markup .= $this->{$Element['handler']}($Element['text']);
    12211406            }
    12221407            else
    12231408            {
    1224                 return;
    1225             }
     1409                $markup .= $Element['text'];
     1410            }
     1411
     1412            $markup .= '</'.$Element['name'].'>';
    12261413        }
    12271414        else
    12281415        {
    1229             return;
    1230         }
    1231 
    1232         $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Link['url']);
    1233 
    1234         if ($Excerpt['text'][0] === '!')
    1235         {
    1236             $Element = array(
    1237                 'name' => 'img',
    1238                 'attributes' => array(
    1239                     'alt' => $Link['text'],
    1240                     'src' => $url,
    1241                 ),
    1242             );
    1243         }
    1244         else
    1245         {
    1246             $Element = array(
    1247                 'name' => 'a',
    1248                 'handler' => 'line',
    1249                 'text' => $Link['text'],
    1250                 'attributes' => array(
    1251                     'href' => $url,
    1252                 ),
    1253             );
    1254         }
    1255 
    1256         if (isset($Link['title']))
    1257         {
    1258             $Element['attributes']['title'] = $Link['title'];
    1259         }
    1260 
    1261         return array(
    1262             'extent' => $extent,
    1263             'element' => $Element,
    1264         );
    1265     }
    1266 
    1267     protected function identifyEmphasis($Excerpt)
    1268     {
    1269         if ( ! isset($Excerpt['text'][1]))
    1270         {
    1271             return;
    1272         }
    1273 
    1274         $marker = $Excerpt['text'][0];
    1275 
    1276         if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
    1277         {
    1278             $emphasis = 'strong';
    1279         }
    1280         elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
    1281         {
    1282             $emphasis = 'em';
    1283         }
    1284         else
    1285         {
    1286             return;
    1287         }
    1288 
    1289         return array(
    1290             'extent' => strlen($matches[0]),
    1291             'element' => array(
    1292                 'name' => $emphasis,
    1293                 'handler' => 'line',
    1294                 'text' => $matches[1],
    1295             ),
    1296         );
    1297     }
    1298 
    1299     #
     1416            $markup .= ' />';
     1417        }
     1418
     1419        return $markup;
     1420    }
     1421
     1422    protected function elements(array $Elements)
     1423    {
     1424        $markup = '';
     1425
     1426        foreach ($Elements as $Element)
     1427        {
     1428            $markup .= "\n" . $this->element($Element);
     1429        }
     1430
     1431        $markup .= "\n";
     1432
     1433        return $markup;
     1434    }
     1435
    13001436    # ~
    1301 
    1302     protected function readPlainText($text)
    1303     {
    1304         $breakMarker = $this->breaksEnabled ? "\n" : "  \n";
    1305 
    1306         $text = str_replace($breakMarker, "<br />\n", $text);
    1307 
    1308         return $text;
    1309     }
    1310 
    1311     #
    1312     # ~
    1313     #
    13141437
    13151438    protected function li($lines)
     
    13331456
    13341457    #
    1335     # Multiton
     1458    # Deprecated Methods
     1459    #
     1460
     1461    function parse($text)
     1462    {
     1463        $markup = $this->text($text);
     1464
     1465        return $markup;
     1466    }
     1467
     1468    #
     1469    # Static Methods
    13361470    #
    13371471
     
    13531487
    13541488    #
    1355     # Deprecated Methods
    1356     #
    1357 
    1358     /**
    1359      * @deprecated in favor of "text"
    1360      */
    1361     function parse($text)
    1362     {
    1363         $markup = $this->text($text);
    1364 
    1365         return $markup;
    1366     }
    1367 
    1368     #
    13691489    # Fields
    13701490    #
    13711491
    1372     protected $Definitions;
    1373 
    1374     #
    1375     # Read-only
     1492    protected $DefinitionData;
     1493
     1494    #
     1495    # Read-Only
    13761496
    13771497    protected $specialCharacters = array(
    1378         '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!',
     1498        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
    13791499    );
    13801500
    13811501    protected $StrongRegex = array(
    1382         '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
    1383         '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us',
     1502        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
     1503        '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
    13841504    );
    13851505
    13861506    protected $EmRegex = array(
    1387         '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
    1388         '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us',
     1507        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
     1508        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
     1509    );
     1510
     1511    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
     1512
     1513    protected $voidElements = array(
     1514        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
    13891515    );
    13901516
  • wp-parsedown/trunk/lib/parsedown/README.md

    r953966 r1153658  
    11## Parsedown
    22
    3 Better [Markdown](http://en.wikipedia.org/wiki/Markdown) parser for PHP.
     3[![Build Status](https://img.shields.io/travis/erusev/parsedown/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown)
     4<!--[![Total Downloads](http://img.shields.io/packagist/dt/erusev/parsedown.svg?style=flat-square)](https://packagist.org/packages/erusev/parsedown)-->
    45
    5 * [Demo](http://parsedown.org/demo)
    6 * [Test Suite](http://parsedown.org/tests/)
     6Better Markdown Parser in PHP
     7
     8[See Demo](http://parsedown.org/demo)
    79
    810### Features
     
    1012* [Fast](http://parsedown.org/speed)
    1113* [Consistent](http://parsedown.org/consistency)
    12 * [GitHub Flavored](https://help.github.com/articles/github-flavored-markdown)
    13 * [Tested](https://travis-ci.org/erusev/parsedown) in PHP 5.2, 5.3, 5.4, 5.5, 5.6 and [hhvm](http://www.hhvm.com/)
    14 * Extensible
    15 * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) <sup>new</sup>
    16 * [JavaScript port](https://github.com/hkdobrev/parsedown.js) under development <sup>new</sup>
     14* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown)
     15* [Tested](http://parsedown.org/tests/) in PHP 5.3, 5.4, 5.5, 5.6 and [HHVM](http://www.hhvm.com/)
     16* [Extensible](https://github.com/erusev/parsedown/wiki/Writing-Extensions)
     17* [Markdown Extra extension](https://github.com/erusev/parsedown-extra)
    1718
    1819### Installation
     
    2829```
    2930
    30 More examples in [the wiki](https://github.com/erusev/parsedown/wiki/Usage).
     31More examples in [the wiki](https://github.com/erusev/parsedown/wiki/Usage) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
    3132
    3233### Questions
    3334
    34 **How does Parsedown work?**<br/>
    35 Parsedown recognises that the Markdown syntax is optimised for humans so it tries to read like one. It goes through text line by line. It looks at how lines start to identify blocks. It looks for special characters to identify inline elements.
     35**How does Parsedown work?**
    3636
    37 **Why doesn’t Parsedown use namespaces?**<br/>
    38 Using namespaces would mean dropping support for PHP 5.2. Since Parsedown is a single class with an uncommon name, making this trade wouldn't make much sense.
     37It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belong to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines).
    3938
    40 **Who uses Parsedown?**<br/>
    41 [phpDocumentor](http://www.phpdoc.org/), [Bolt CMS](http://bolt.cm/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references).
     39We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages.
     40
     41**Is it compliant with CommonMark?**
     42
     43It passes most of the CommonMark tests. Most of the tests that don't pass deal with cases that are quite uncommon. Still, as CommonMark matures, compliance should improve.
     44
     45**Who uses it?**
     46
     47[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/),  [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references).
     48
     49**How can I help?**
     50
     51Use it, star it, share it and if you feel generous, [donate some money](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).
  • wp-parsedown/trunk/readme.txt

    r982893 r1153658  
    44Tags: markdown, editor, parsedown
    55Requires at least: 3.0
    6 Tested up to: 4.0
    7 Stable tag: 0.1.1
     6Tested up to: 4.2.1
     7Stable tag: 0.3
    88License: GPLv3
    99License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    2323== Changelog ==
    2424
     25= 0.3 =
     26*2015-05-05*
     27
     28* updating Parsedown and Parsedown Extra libraries
     29* removing wp-common dependancy
     30* changing debug log method
     31
     32= 0.2 =
     33*2014-09-23*
     34
     35* added removal of wpautop filters; they are not needed when Parsedown is doing it's job
     36* updated parsedown libs
     37
    2538= 0.1.1 =
    2639*2014-09-06*
  • wp-parsedown/trunk/wp-parsedown.php

    r982893 r1153658  
    44Plugin URI: https://github.com/petermolnar/wp-parsedown
    55Description: [Parsedown Extra](www.parsedown.org/demo?extra=1) on-the-fly
    6 Version: 0.1.1
     6Version: 0.3
    77Author: Peter Molnar <[email protected]>
    88Author URI: https://petermolnar.eu/
     
    1313if ( ! class_exists( 'WP_PARSEDOWN' ) ) :
    1414
    15 /* get the plugin abstract class*/
    16 include_once ( dirname(__FILE__) . '/wp-common/plugin_abstract.php' );
    1715include_once ( dirname(__FILE__) . '/lib/parsedown/Parsedown.php');
    1816include_once ( dirname(__FILE__) . '/lib/parsedown-extra/ParsedownExtra.php');
     
    2119 * main wp-ghost class
    2220 */
    23 class WP_PARSEDOWN extends PluginAbstract {
    24     const key_save = 'saved';
    25     const key_delete = 'deleted';
    26     private $parsedown = null;
     21class WP_PARSEDOWN {
    2722
    28     /**
    29      *
    30      */
    31     public function plugin_post_construct () {
    32         $this->plugin_url = plugin_dir_url( __FILE__ );
    33         $this->plugin_dir = plugin_dir_path( __FILE__ );
    34 
    35         $this->common_url = $this->plugin_url . self::common_slug;
    36         $this->common_dir = $this->plugin_dir . self::common_slug;
    37 
    38         $this->admin_css_handle = $this->plugin_constant . '-admin-css';
    39         $this->admin_css_url = $this->common_url . 'wp-admin.css';
    40 
    41         $this->parsedown = new ParsedownExtra();
     23    public function __construct () {
     24        add_action( 'init', array(&$this,'init'));
    4225    }
    4326
    44     /**
    45      * init hook function runs before admin panel hook, themeing and options read
    46      */
    47     public function plugin_pre_init() {
    48 
     27    public function init () {
     28        remove_filter( 'the_content', 'wpautop' );
     29        remove_filter( 'the_excerpt', 'wpautop' );
     30        add_filter( 'the_content', array( &$this, 'parsedown'), 8, 1 );
    4931    }
    5032
    51     /**
    52      * additional init, steps that needs the plugin options
    53      *
    54      */
    55     public function plugin_post_init () {
    56         /* display markdown */
    57         add_filter( 'the_content', array(&$this, 'markdown_on_the_fly'), 9 );
     33    public function parsedown ( $markdown ) {
     34        $post = get_post();
    5835
    59     }
    60 
    61 
    62     /**
    63      * activation hook function, to be extended
    64      */
    65     public function plugin_activate() {
    66         /* we leave this empty to avoid not detecting WP network correctly */
    67     }
    68 
    69     /**
    70      * deactivation hook function, to be extended
    71      */
    72     public function plugin_deactivate () {
    73     }
    74 
    75     /**
    76      * uninstall hook function, to be extended
    77      */
    78     public function plugin_uninstall( $delete_options = true ) {
    79     }
    80 
    81     /**
    82      * extending admin init
    83      *
    84      */
    85     public function plugin_extend_admin_init () {
    86     }
    87 
    88     /**
    89      * admin help panel
    90      */
    91     public function plugin_admin_help($contextual_help, $screen_id ) {
    92 
    93         /* add our page only if the screenid is correct */
    94         if ( strpos( $screen_id, $this->plugin_settings_page ) ) {
    95             $contextual_help = __('<p>Please visit <a href="http://wordpress.org/support/plugin/wp-ghost">the official support forum of the plugin</a> for help.</p>', $this->plugin_constant );
     36        if ( defined( 'WP_DEBUG' ) && WP_DEBUG == true ) {
     37            $message = sprintf ( __('parsing post: %s', $this->plugin_constant),  $post->ID );
     38            error_log(  __CLASS__ . ": " . $message );
    9639        }
    9740
    98         return $contextual_help;
    99     }
    100 
    101     /**
    102      * admin panel, the admin page displayed for plugin settings
    103      */
    104     public function plugin_admin_panel() {
    105         /**
    106          * security, if somehow we're running without WordPress security functions
    107          */
    108         if( ! function_exists( 'current_user_can' ) || ! current_user_can( 'manage_options' ) ){
    109             die( );
    110         }
    111         ?>
    112 
    113         <div class="wrap">
    114 
    115         <script>
    116             jQuery(document).ready(function($) {
    117                 jQuery( "#<?php echo $this->plugin_constant ?>-settings" ).tabs();
    118             });
    119         </script>
    120 
    121         <?php
    122 
    123         /* display donation form */
    124         //$this->plugin_donation_form();
    125 
    126         /**
    127          * if options were saved, display saved message
    128          */
    129         if (isset($_GET[ self::key_save ]) && $_GET[ self::key_save ]=='true' || $this->status == 1) { ?>
    130             <div class='updated settings-error'><p><strong><?php _e( 'Settings saved.' , $this->plugin_constant ) ?></strong></p></div>
    131         <?php }
    132 
    133         /**
    134          * if options were delete, display delete message
    135          */
    136         if (isset($_GET[ self::key_delete ]) && $_GET[ self::key_delete ]=='true' || $this->status == 2) { ?>
    137             <div class='error'><p><strong><?php _e( 'Plugin options deleted.' , $this->plugin_constant ) ?></strong></p></div>
    138         <?php }
    139 
    140         /**
    141          * the admin panel itself
    142          */
    143         ?>
    144 
    145         <h2><?php printf ( __( '%s settings', $this->plugin_constant ), $this->plugin_name ) ; ?></h2>
    146 
    147         <form autocomplete="off" method="post" action="#" id="<?php echo $this->plugin_constant ?>-settings" class="plugin-admin">
    148 
    149             <?php wp_nonce_field( $this->plugin_constant ); ?>
    150             <ul class="tabs">
    151                 <li><a href="#<?php echo $this->plugin_constant ?>-general" class="wp-switch-editor"><?php _e( 'Generic settings', $this->plugin_constant ); ?></a></li>
    152             </ul>
    153 
    154             <fieldset id="<?php echo $this->plugin_constant ?>-general">
    155             <legend><?php _e( 'General settings', $this->plugin_constant ); ?></legend>
    156             <dl>
    157                 <dt>
    158                     <label for="debug"><?php _e("Enable debug logging?", $this->plugin_constant); ?></label>
    159                 </dt>
    160                 <dd>
    161                     <input type="checkbox" name="debug" id="debug" value="1" <?php checked($this->options['debug'],true); ?> />
    162                     <span class="description"><?php _e('Enables log messages; if <a href="http://codex.wordpress.org/WP_DEBUG">WP_DEBUG</a> is enabled, notices and info level is displayed as well, otherwie only ERRORS are logged.', $this->plugin_constant); ?></span>
    163                 </dd>
    164             </dl>
    165             </fieldset>
    166             <p class="clear">
    167                 <input class="button-primary" type="submit" name="<?php echo $this->button_save ?>" id="<?php echo $this->button_save ?>" value="<?php _e('Save Changes', $this->plugin_constant ) ?>" />
    168             </p>
    169         </form>
    170         </div>
    171         <?php
    172     }
    173 
    174     /**
    175      * extending options_save
    176      *
    177      */
    178     public function plugin_extend_options_save( $activating ) {
    179     }
    180 
    181     /**
    182      * read hook; needs to be implemented
    183      */
    184     public function plugin_extend_options_read( &$options ) {
    185     }
    186 
    187     /**
    188      * options delete hook; needs to be implemented
    189      */
    190     public function plugin_extend_options_delete(  ) {
    191     }
    192 
    193     /**
    194      * need to do migrations from previous versions of the plugin
    195      *
    196      */
    197     public function plugin_options_migrate( &$options ) {
    198     }
    199 
    200 
    201 
    202     /**
    203      * log wrapper to include options
    204      *
    205      */
    206     public function log ( $message, $log_level = LOG_WARNING ) {
    207         if ( !isset ( $this->options['debug'] ) || $this->options['debug'] != 1 )
    208             return false;
    209         else
    210             $this->utils->log ( $this->plugin_constant, $message, $log_level );
    211     }
    212 
    213     /**
    214      *
    215      */
    216     public function markdown_on_the_fly ( $markdown ) {
    217         $post = get_post();
    218         $this->log ( sprintf ( __('parsing post: %s', $this->plugin_constant),  $post->ID ) );
    219         return $this->parsedown->text ( $markdown );
     41        $parsedown = new ParsedownExtra();
     42        $parsedown->setBreaksEnabled(true);
     43        return $parsedown->text ( $markdown );
    22044    }
    22145
    22246}
    22347
     48$wp_parsedown = new WP_PARSEDOWN ();
     49
    22450endif;
    225 
    226 
    227 $wp_parsedown_defaults = array (
    228     'debug' => 0,
    229 );
    230 
    231 $wp_parsedown = new WP_PARSEDOWN ( 'wp-parsedown', '0.1', 'WP-Parsedown', $wp_parsedown_defaults );
    232 
    233 
    234 ?>
Note: See TracChangeset for help on using the changeset viewer.