]>
git.immae.eu Git - github/shaarli/Shaarli.git/blob - plugins/markdown/Parsedown.php
11 # For the full license information, view the LICENSE file that was distributed
12 # with this source code.
20 const version
= '1.6.0';
26 # make sure no definitions are set
27 $this->DefinitionData
= array();
29 # standardize line breaks
30 $text = str_replace(array("\r\n", "\r"), "\n", $text);
32 # remove surrounding line breaks
33 $text = trim($text, "\n");
35 # split text into lines
36 $lines = explode("\n", $text);
38 # iterate through lines to identify blocks
39 $markup = $this->lines($lines);
42 $markup = trim($markup, "\n");
51 function setBreaksEnabled($breaksEnabled)
53 $this->breaksEnabled
= $breaksEnabled;
58 protected $breaksEnabled;
60 function setMarkupEscaped($markupEscaped)
62 $this->markupEscaped
= $markupEscaped;
67 protected $markupEscaped;
69 function setUrlsLinked($urlsLinked)
71 $this->urlsLinked
= $urlsLinked;
76 protected $urlsLinked = true;
82 protected $BlockTypes = array(
83 '#' => array('Header'),
84 '*' => array('Rule', 'List'),
86 '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
97 ':' => array('Table'),
98 '<' => array('Comment', 'Markup'),
99 '=' => array('SetextHeader'),
100 '>' => array('Quote'),
101 '[' => array('Reference'),
102 '_' => array('Rule'),
103 '`' => array('FencedCode'),
104 '|' => array('Table'),
105 '~' => array('FencedCode'),
110 protected $unmarkedBlockTypes = array(
118 private function lines(array $lines)
120 $CurrentBlock = null;
122 foreach ($lines as $line)
124 if (chop($line) === '')
126 if (isset($CurrentBlock))
128 $CurrentBlock['interrupted'] = true;
134 if (strpos($line, "\t") !== false)
136 $parts = explode("\t", $line);
142 foreach ($parts as $part)
144 $shortage = 4 - mb_strlen($line, 'utf-8') %
4;
146 $line .= str_repeat(' ', $shortage);
153 while (isset($line[$indent]) and $line[$indent] === ' ')
158 $text = $indent > 0 ? substr($line, $indent) : $line;
162 $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
166 if (isset($CurrentBlock['continuable']))
168 $Block = $this->{'block'.$CurrentBlock
['type'].'Continue'}($Line, $CurrentBlock);
172 $CurrentBlock = $Block;
178 if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
180 $CurrentBlock = $this->{'block'.$CurrentBlock
['type'].'Complete'}($CurrentBlock);
191 $blockTypes = $this->unmarkedBlockTypes
;
193 if (isset($this->BlockTypes
[$marker]))
195 foreach ($this->BlockTypes
[$marker] as $blockType)
197 $blockTypes []= $blockType;
204 foreach ($blockTypes as $blockType)
206 $Block = $this->{'block'.$blockType
}($Line, $CurrentBlock);
210 $Block['type'] = $blockType;
212 if ( ! isset($Block['identified']))
214 $Blocks []= $CurrentBlock;
216 $Block['identified'] = true;
219 if (method_exists($this, 'block'.$blockType.'Continue'))
221 $Block['continuable'] = true;
224 $CurrentBlock = $Block;
232 if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
234 $CurrentBlock['element']['text'] .= "\n".$text;
238 $Blocks []= $CurrentBlock;
240 $CurrentBlock = $this->paragraph($Line);
242 $CurrentBlock['identified'] = true;
248 if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
250 $CurrentBlock = $this->{'block'.$CurrentBlock
['type'].'Complete'}($CurrentBlock);
255 $Blocks []= $CurrentBlock;
263 foreach ($Blocks as $Block)
265 if (isset($Block['hidden']))
271 $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
284 protected function blockCode($Line, $Block = null)
286 if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
291 if ($Line['indent'] >= 4)
293 $text = substr($Line['body'], 4);
298 'handler' => 'element',
310 protected function blockCodeContinue($Line, $Block)
312 if ($Line['indent'] >= 4)
314 if (isset($Block['interrupted']))
316 $Block['element']['text']['text'] .= "\n";
318 unset($Block['interrupted']);
321 $Block['element']['text']['text'] .= "\n";
323 $text = substr($Line['body'], 4);
325 $Block['element']['text']['text'] .= $text;
331 protected function blockCodeComplete($Block)
333 $text = $Block['element']['text']['text'];
335 $text = htmlspecialchars($text, ENT_NOQUOTES
, 'UTF-8');
337 $Block['element']['text']['text'] = $text;
345 protected function blockComment($Line)
347 if ($this->markupEscaped
)
352 if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
355 'markup' => $Line['body'],
358 if (preg_match('/-->$/', $Line['text']))
360 $Block['closed'] = true;
367 protected function blockCommentContinue($Line, array $Block)
369 if (isset($Block['closed']))
374 $Block['markup'] .= "\n" . $Line['body'];
376 if (preg_match('/-->$/', $Line['text']))
378 $Block['closed'] = true;
387 protected function blockFencedCode($Line)
389 if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
396 if (isset($matches[1]))
398 $class = 'language-'.$matches[1];
400 $Element['attributes'] = array(
406 'char' => $Line['text'][0],
409 'handler' => 'element',
418 protected function blockFencedCodeContinue($Line, $Block)
420 if (isset($Block['complete']))
425 if (isset($Block['interrupted']))
427 $Block['element']['text']['text'] .= "\n";
429 unset($Block['interrupted']);
432 if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
434 $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
436 $Block['complete'] = true;
441 $Block['element']['text']['text'] .= "\n".$Line['body'];;
446 protected function blockFencedCodeComplete($Block)
448 $text = $Block['element']['text']['text'];
450 $text = htmlspecialchars($text, ENT_NOQUOTES
, 'UTF-8');
452 $Block['element']['text']['text'] = $text;
460 protected function blockHeader($Line)
462 if (isset($Line['text'][1]))
466 while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
476 $text = trim($Line['text'], '# ');
480 'name' => 'h' . min(6, $level),
493 protected function blockList($Line)
495 list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
497 if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
500 'indent' => $Line['indent'],
501 'pattern' => $pattern,
504 'handler' => 'elements',
508 $Block['li'] = array(
516 $Block['element']['text'] []= & $Block['li'];
522 protected function blockListContinue($Line, array $Block)
524 if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
526 if (isset($Block['interrupted']))
528 $Block['li']['text'] []= '';
530 unset($Block['interrupted']);
535 $text = isset($matches[1]) ? $matches[1] : '';
537 $Block['li'] = array(
545 $Block['element']['text'] []= & $Block['li'];
550 if ($Line['text'][0] === '[' and $this->blockReference($Line))
555 if ( ! isset($Block['interrupted']))
557 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
559 $Block['li']['text'] []= $text;
564 if ($Line['indent'] > 0)
566 $Block['li']['text'] []= '';
568 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
570 $Block['li']['text'] []= $text;
572 unset($Block['interrupted']);
581 protected function blockQuote($Line)
583 if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
587 'name' => 'blockquote',
588 'handler' => 'lines',
589 'text' => (array) $matches[1],
597 protected function blockQuoteContinue($Line, array $Block)
599 if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
601 if (isset($Block['interrupted']))
603 $Block['element']['text'] []= '';
605 unset($Block['interrupted']);
608 $Block['element']['text'] []= $matches[1];
613 if ( ! isset($Block['interrupted']))
615 $Block['element']['text'] []= $Line['text'];
624 protected function blockRule($Line)
626 if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
641 protected function blockSetextHeader($Line, array $Block = null)
643 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
648 if (chop($Line['text'], $Line['text'][0]) === '')
650 $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
659 protected function blockMarkup($Line)
661 if ($this->markupEscaped
)
666 if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*(\/)?>/', $Line['text'], $matches))
668 $element = strtolower($matches[1]);
670 if (in_array($element, $this->textLevelElements
))
676 'name' => $matches[1],
678 'markup' => $Line['text'],
681 $length = strlen($matches[0]);
683 $remainder = substr($Line['text'], $length);
685 if (trim($remainder) === '')
687 if (isset($matches[2]) or in_array($matches[1], $this->voidElements
))
689 $Block['closed'] = true;
691 $Block['void'] = true;
696 if (isset($matches[2]) or in_array($matches[1], $this->voidElements
))
701 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
703 $Block['closed'] = true;
711 protected function blockMarkupContinue($Line, array $Block)
713 if (isset($Block['closed']))
718 if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*>/i', $Line['text'])) # open
723 if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
725 if ($Block['depth'] > 0)
731 $Block['closed'] = true;
735 if (isset($Block['interrupted']))
737 $Block['markup'] .= "\n";
739 unset($Block['interrupted']);
742 $Block['markup'] .= "\n".$Line['body'];
750 protected function blockReference($Line)
752 if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
754 $id = strtolower($matches[1]);
757 'url' => $matches[2],
761 if (isset($matches[3]))
763 $Data['title'] = $matches[3];
766 $this->DefinitionData
['Reference'][$id] = $Data;
779 protected function blockTable($Line, array $Block = null)
781 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
786 if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
788 $alignments = array();
790 $divider = $Line['text'];
792 $divider = trim($divider);
793 $divider = trim($divider, '|');
795 $dividerCells = explode('|', $divider);
797 foreach ($dividerCells as $dividerCell)
799 $dividerCell = trim($dividerCell);
801 if ($dividerCell === '')
808 if ($dividerCell[0] === ':')
813 if (substr($dividerCell, - 1) === ':')
815 $alignment = $alignment === 'left' ? 'center' : 'right';
818 $alignments []= $alignment;
823 $HeaderElements = array();
825 $header = $Block['element']['text'];
827 $header = trim($header);
828 $header = trim($header, '|');
830 $headerCells = explode('|', $header);
832 foreach ($headerCells as $index => $headerCell)
834 $headerCell = trim($headerCell);
836 $HeaderElement = array(
838 'text' => $headerCell,
842 if (isset($alignments[$index]))
844 $alignment = $alignments[$index];
846 $HeaderElement['attributes'] = array(
847 'style' => 'text-align: '.$alignment.';',
851 $HeaderElements []= $HeaderElement;
857 'alignments' => $alignments,
858 'identified' => true,
861 'handler' => 'elements',
865 $Block['element']['text'] []= array(
867 'handler' => 'elements',
870 $Block['element']['text'] []= array(
872 'handler' => 'elements',
876 $Block['element']['text'][0]['text'] []= array(
878 'handler' => 'elements',
879 'text' => $HeaderElements,
886 protected function blockTableContinue($Line, array $Block)
888 if (isset($Block['interrupted']))
893 if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
897 $row = $Line['text'];
900 $row = trim($row, '|');
902 preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
904 foreach ($matches[0] as $index => $cell)
914 if (isset($Block['alignments'][$index]))
916 $Element['attributes'] = array(
917 'style' => 'text-align: '.$Block['alignments'][$index].';',
921 $Elements []= $Element;
926 'handler' => 'elements',
930 $Block['element']['text'][1]['text'] []= $Element;
940 protected function paragraph($Line)
945 'text' => $Line['text'],
957 protected $InlineTypes = array(
958 '"' => array('SpecialCharacter'),
959 '!' => array('Image'),
960 '&' => array('SpecialCharacter'),
961 '*' => array('Emphasis'),
963 '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
964 '>' => array('SpecialCharacter'),
965 '[' => array('Link'),
966 '_' => array('Emphasis'),
967 '`' => array('Code'),
968 '~' => array('Strikethrough'),
969 '\\' => array('EscapeSequence'),
974 protected $inlineMarkerList = '!"*_&[:<>`~\\';
980 public function line($text)
984 # $excerpt is based on the first occurrence of a marker
986 while ($excerpt = strpbrk($text, $this->inlineMarkerList
))
988 $marker = $excerpt[0];
990 $markerPosition = strpos($text, $marker);
992 $Excerpt = array('text' => $excerpt, 'context' => $text);
994 foreach ($this->InlineTypes
[$marker] as $inlineType)
996 $Inline = $this->{'inline'.$inlineType
}($Excerpt);
998 if ( ! isset($Inline))
1003 # makes sure that the inline belongs to "our" marker
1005 if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1010 # sets a default inline position
1012 if ( ! isset($Inline['position']))
1014 $Inline['position'] = $markerPosition;
1017 # the text that comes before the inline
1018 $unmarkedText = substr($text, 0, $Inline['position']);
1020 # compile the unmarked text
1021 $markup .= $this->unmarkedText($unmarkedText);
1023 # compile the inline
1024 $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1026 # remove the examined text
1027 $text = substr($text, $Inline['position'] +
$Inline['extent']);
1032 # the marker does not belong to an inline
1034 $unmarkedText = substr($text, 0, $markerPosition +
1);
1036 $markup .= $this->unmarkedText($unmarkedText);
1038 $text = substr($text, $markerPosition +
1);
1041 $markup .= $this->unmarkedText($text);
1050 protected function inlineCode($Excerpt)
1052 $marker = $Excerpt['text'][0];
1054 if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1056 $text = $matches[2];
1057 $text = htmlspecialchars($text, ENT_NOQUOTES
, 'UTF-8');
1058 $text = preg_replace("/[ ]*\n/", ' ', $text);
1061 'extent' => strlen($matches[0]),
1070 protected function inlineEmailTag($Excerpt)
1072 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1076 if ( ! isset($matches[2]))
1078 $url = 'mailto:' . $url;
1082 'extent' => strlen($matches[0]),
1085 'text' => $matches[1],
1086 'attributes' => array(
1094 protected function inlineEmphasis($Excerpt)
1096 if ( ! isset($Excerpt['text'][1]))
1101 $marker = $Excerpt['text'][0];
1103 if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex
[$marker], $Excerpt['text'], $matches))
1105 $emphasis = 'strong';
1107 elseif (preg_match($this->EmRegex
[$marker], $Excerpt['text'], $matches))
1117 'extent' => strlen($matches[0]),
1119 'name' => $emphasis,
1120 'handler' => 'line',
1121 'text' => $matches[1],
1126 protected function inlineEscapeSequence($Excerpt)
1128 if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters
))
1131 'markup' => $Excerpt['text'][1],
1137 protected function inlineImage($Excerpt)
1139 if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1144 $Excerpt['text']= substr($Excerpt['text'], 1);
1146 $Link = $this->inlineLink($Excerpt);
1154 'extent' => $Link['extent'] +
1,
1157 'attributes' => array(
1158 'src' => $Link['element']['attributes']['href'],
1159 'alt' => $Link['element']['text'],
1164 $Inline['element']['attributes'] +
= $Link['element']['attributes'];
1166 unset($Inline['element']['attributes']['href']);
1171 protected function inlineLink($Excerpt)
1175 'handler' => 'line',
1177 'attributes' => array(
1185 $remainder = $Excerpt['text'];
1187 if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
1189 $Element['text'] = $matches[1];
1191 $extent +
= strlen($matches[0]);
1193 $remainder = substr($remainder, $extent);
1200 if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
1202 $Element['attributes']['href'] = $matches[1];
1204 if (isset($matches[2]))
1206 $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1209 $extent +
= strlen($matches[0]);
1213 if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1215 $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1216 $definition = strtolower($definition);
1218 $extent +
= strlen($matches[0]);
1222 $definition = strtolower($Element['text']);
1225 if ( ! isset($this->DefinitionData
['Reference'][$definition]))
1230 $Definition = $this->DefinitionData
['Reference'][$definition];
1232 $Element['attributes']['href'] = $Definition['url'];
1233 $Element['attributes']['title'] = $Definition['title'];
1236 $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
1239 'extent' => $extent,
1240 'element' => $Element,
1244 protected function inlineMarkup($Excerpt)
1246 if ($this->markupEscaped
or strpos($Excerpt['text'], '>') === false)
1251 if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1254 'markup' => $matches[0],
1255 'extent' => strlen($matches[0]),
1259 if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1262 'markup' => $matches[0],
1263 'extent' => strlen($matches[0]),
1267 if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1270 'markup' => $matches[0],
1271 'extent' => strlen($matches[0]),
1276 protected function inlineSpecialCharacter($Excerpt)
1278 if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1281 'markup' => '&',
1286 $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1288 if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1291 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1297 protected function inlineStrikethrough($Excerpt)
1299 if ( ! isset($Excerpt['text'][1]))
1304 if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1307 'extent' => strlen($matches[0]),
1310 'text' => $matches[1],
1311 'handler' => 'line',
1317 protected function inlineUrl($Excerpt)
1319 if ($this->urlsLinked
!== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1324 if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE
))
1327 'extent' => strlen($matches[0][0]),
1328 'position' => $matches[0][1],
1331 'text' => $matches[0][0],
1332 'attributes' => array(
1333 'href' => $matches[0][0],
1342 protected function inlineUrlTag($Excerpt)
1344 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1346 $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
1349 'extent' => strlen($matches[0]),
1353 'attributes' => array(
1363 protected function unmarkedText($text)
1365 if ($this->breaksEnabled
)
1367 $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1371 $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1372 $text = str_replace(" \n", "\n", $text);
1382 protected function element(array $Element)
1384 $markup = '<'.$Element['name'];
1386 if (isset($Element['attributes']))
1388 foreach ($Element['attributes'] as $name => $value)
1390 if ($value === null)
1395 $markup .= ' '.$name.'="'.$value.'"';
1399 if (isset($Element['text']))
1403 if (isset($Element['handler']))
1405 $markup .= $this->{$Element
['handler']}($Element['text']);
1409 $markup .= $Element['text'];
1412 $markup .= '</'.$Element['name'].'>';
1422 protected function elements(array $Elements)
1426 foreach ($Elements as $Element)
1428 $markup .= "\n" . $this->element($Element);
1438 protected function li($lines)
1440 $markup = $this->lines($lines);
1442 $trimmedMarkup = trim($markup);
1444 if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1446 $markup = $trimmedMarkup;
1447 $markup = substr($markup, 3);
1449 $position = strpos($markup, "</p>");
1451 $markup = substr_replace($markup, '', $position, 4);
1458 # Deprecated Methods
1461 function parse($text)
1463 $markup = $this->text($text);
1472 static function instance($name = 'default')
1474 if (isset(self
::$instances[$name]))
1476 return self
::$instances[$name];
1479 $instance = new static();
1481 self
::$instances[$name] = $instance;
1486 private static $instances = array();
1492 protected $DefinitionData;
1497 protected $specialCharacters = array(
1498 '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1501 protected $StrongRegex = array(
1502 '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1503 '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1506 protected $EmRegex = array(
1507 '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1508 '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1511 protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1513 protected $voidElements = array(
1514 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1517 protected $textLevelElements = array(
1518 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1519 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1520 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1521 'q', 'rt', 'ins', 'font', 'strong',
1522 's', 'tt', 'sub', 'mark',
1523 'u', 'xm', 'sup', 'nobr',