diff --git a/Parsedown.php b/Parsedown.php index 26de03f..5468c06 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -972,7 +972,8 @@ class Parsedown '!' => array('Image'), '&' => array('SpecialCharacter'), '*' => array('Emphasis'), - '<' => array('Url', 'Email', 'Markup', 'SpecialCharacter'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), '>' => array('SpecialCharacter'), '[' => array('Link'), '_' => array('Emphasis'), @@ -983,7 +984,7 @@ class Parsedown # ~ - protected $inlineMarkerList = '!"*_&[<>`~\\'; + protected $inlineMarkerList = '!"*_&[:<>`~\\'; # # ~ @@ -993,43 +994,53 @@ class Parsedown { $markup = ''; - $remainder = $text; + $unexaminedText = $text; $markerPosition = 0; - while ($excerpt = strpbrk($remainder, $this->inlineMarkerList)) + while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList)) { $marker = $excerpt[0]; - $markerPosition += strpos($remainder, $marker); + $markerPosition += strpos($unexaminedText, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); foreach ($this->InlineTypes[$marker] as $inlineType) { - $handler = 'inline'.$inlineType; - - $Inline = $this->$handler($excerpt); + $Inline = $this->{'inline'.$inlineType}($Excerpt); if ( ! isset($Inline)) { continue; } - $plainText = substr($text, 0, $markerPosition); + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker + { + continue; + } - $markup .= $this->unmarkedText($plainText); + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + $unmarkedText = substr($text, 0, $Inline['position']); + + $markup .= $this->unmarkedText($unmarkedText); $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); - $text = substr($text, $markerPosition + $Inline['extent']); + $text = substr($text, $Inline['position'] + $Inline['extent']); - $remainder = $text; + $unexaminedText = $text; $markerPosition = 0; continue 2; } - $remainder = substr($excerpt, 1); + $unexaminedText = substr($excerpt, 1); $markerPosition ++; } @@ -1043,11 +1054,11 @@ class Parsedown # ~ # - protected function inlineCode($excerpt) + protected function inlineCode($Excerpt) { - $marker = $excerpt[0]; + $marker = $Excerpt['text'][0]; - if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $excerpt, $matches)) + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { $url = $matches[1]; @@ -1087,20 +1098,20 @@ class Parsedown } } - protected function inlineEmphasis($excerpt) + protected function inlineEmphasis($Excerpt) { - if ( ! isset($excerpt[1])) + if ( ! isset($Excerpt['text'][1])) { return; } - $marker = $excerpt[0]; + $marker = $Excerpt['text'][0]; - if ($excerpt[1] === $marker and preg_match($this->StrongRegex[$marker], $excerpt, $matches)) + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { $emphasis = 'strong'; } - elseif (preg_match($this->EmRegex[$marker], $excerpt, $matches)) + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) { $emphasis = 'em'; } @@ -1119,60 +1130,52 @@ class Parsedown ); } - protected function inlineEscapeSequence($excerpt) + protected function inlineEscapeSequence($Excerpt) { - if (isset($excerpt[1]) and in_array($excerpt[1], $this->specialCharacters)) + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { return array( - 'markup' => $excerpt[1], + 'markup' => $Excerpt['text'][1], 'extent' => 2, ); } } - protected function inlineImage($excerpt) + protected function inlineImage($Excerpt) { - if ( ! isset($excerpt[1]) or $excerpt[1] !== '[') + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') { return; } - $excerpt = substr($excerpt, 1); + $Excerpt['text']= substr($Excerpt['text'], 1); - $InlineLink = $this->inlineLink($excerpt); + $Link = $this->inlineLink($Excerpt); - if ($InlineLink === null) + if ($Link === null) { return; } $Inline = array( - 'extent' => $InlineLink['extent'] + 1, + 'extent' => $Link['extent'] + 1, 'element' => array( 'name' => 'img', 'attributes' => array( - 'src' => $InlineLink['element']['attributes']['href'], - 'alt' => $InlineLink['element']['text'], + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], ), ), ); - $Inline['element']['attributes'] += $InlineLink['element']['attributes']; + $Inline['element']['attributes'] += $Link['element']['attributes']; unset($Inline['element']['attributes']['href']); return $Inline; } - protected function inlineLessThan() - { - return array( - 'markup' => '<', - 'extent' => 1, - ); - } - - protected function inlineLink($excerpt) + protected function inlineLink($Excerpt) { $Element = array( 'name' => 'a', @@ -1186,7 +1189,7 @@ class Parsedown $extent = 0; - $remainder = $excerpt; + $remainder = $Excerpt['text']; if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) { @@ -1245,14 +1248,14 @@ class Parsedown ); } - protected function inlineMarkup($excerpt) + protected function inlineMarkup($Excerpt) { - if ($this->markupEscaped or strpos($excerpt, '>') === false) + if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) { return; } - if ($excerpt[1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $excerpt, $matches)) + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], @@ -1260,7 +1263,7 @@ class Parsedown ); } - if ($excerpt[1] === '!' and preg_match('/^/s', $excerpt, $matches)) + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], @@ -1268,7 +1271,7 @@ class Parsedown ); } - if ($excerpt[1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $excerpt, $matches)) + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], @@ -1277,9 +1280,9 @@ class Parsedown } } - protected function inlineSpecialCharacter($excerpt) + protected function inlineSpecialCharacter($Excerpt) { - if ($excerpt[0] === '&' and ! preg_match('/^&#?\w+;/', $excerpt)) + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) { return array( 'markup' => '&', @@ -1289,23 +1292,23 @@ class Parsedown $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); - if (isset($SpecialCharacter[$excerpt[0]])) + if (isset($SpecialCharacter[$Excerpt['text'][0]])) { return array( - 'markup' => '&'.$SpecialCharacter[$excerpt[0]].';', + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', 'extent' => 1, ); } } - protected function inlineStrikethrough($excerpt) + protected function inlineStrikethrough($Excerpt) { - if ( ! isset($excerpt[1])) + if ( ! isset($Excerpt['text'][1])) { return; } - if ($excerpt[1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $excerpt, $matches)) + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) { return array( 'extent' => strlen($matches[0]), @@ -1318,9 +1321,34 @@ class Parsedown } } - protected function inlineUrl($excerpt) + protected function inlineUrl($Excerpt) { - if (strpos($excerpt, '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $excerpt, $matches)) + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $matches[0][0], + 'attributes' => array( + 'href' => $matches[0][0], + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); @@ -1345,21 +1373,6 @@ class Parsedown # ~ protected function unmarkedText($text) - { - foreach ($this->unmarkedInlineTypes as $snippet => $inlineType) - { - if (strpos($text, $snippet) !== false) - { - $text = $this->{'unmarkedInline'.$inlineType}($text); - } - } - - return $text; - } - - # ~ - - protected function unmarkedInlineBreak($text) { if ($this->breaksEnabled) { @@ -1374,35 +1387,6 @@ class Parsedown return $text; } - protected function unmarkedInlineUrl($text) - { - if ($this->urlsLinked !== true) - { - return $text; - } - - $re = '/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui'; - - $offset = 0; - - while (strpos($text, '://', $offset) and preg_match($re, $text, $matches, PREG_OFFSET_CAPTURE, $offset)) - { - $url = $matches[0][0]; - - $urlLength = strlen($url); - $urlPosition = $matches[0][1]; - - $markup = ''.$url.''; - $markupLength = strlen($markup); - - $text = substr_replace($text, $markup, $urlPosition, $urlLength); - - $offset = $urlPosition + $markupLength; - } - - return $text; - } - # # Handlers #