11 # For the full license information, view the LICENSE file that was distributed
12 # with this source code.
20 const version = '1.5.1';
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 $DefinitionTypes = array(
111 '[' => array('Reference'),
116 protected $unmarkedBlockTypes = array(
124 private function lines(array $lines)
126 $CurrentBlock = null;
128 foreach ($lines as $line)
130 if (chop($line) === '')
132 if (isset($CurrentBlock))
134 $CurrentBlock['interrupted'] = true;
140 if (strpos($line, "\t") !== false)
142 $parts = explode("\t", $line);
148 foreach ($parts as $part)
150 $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
152 $line .= str_repeat(' ', $shortage);
159 while (isset($line[$indent]) and $line[$indent] === ' ')
164 $text = $indent > 0 ? substr($line, $indent) : $line;
168 $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
172 if (isset($CurrentBlock['incomplete']))
174 $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
178 $CurrentBlock = $Block;
184 if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
186 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
189 unset($CurrentBlock['incomplete']);
199 $blockTypes = $this->unmarkedBlockTypes;
201 if (isset($this->BlockTypes[$marker]))
203 foreach ($this->BlockTypes[$marker] as $blockType)
205 $blockTypes []= $blockType;
212 foreach ($blockTypes as $blockType)
214 $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
218 $Block['type'] = $blockType;
220 if ( ! isset($Block['identified']))
222 $Blocks []= $CurrentBlock;
224 $Block['identified'] = true;
227 if (method_exists($this, 'block'.$blockType.'Continue'))
229 $Block['incomplete'] = true;
232 $CurrentBlock = $Block;
240 if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
242 $CurrentBlock['element']['text'] .= "\n".$text;
246 $Blocks []= $CurrentBlock;
248 $CurrentBlock = $this->paragraph($Line);
250 $CurrentBlock['identified'] = true;
256 if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
258 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
263 $Blocks []= $CurrentBlock;
271 foreach ($Blocks as $Block)
273 if (isset($Block['hidden']))
279 $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
292 protected function blockCode($Line, $Block = null)
294 if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
299 if ($Line['indent'] >= 4)
301 $text = substr($Line['body'], 4);
306 'handler' => 'element',
318 protected function blockCodeContinue($Line, $Block)
320 if ($Line['indent'] >= 4)
322 if (isset($Block['interrupted']))
324 $Block['element']['text']['text'] .= "\n";
326 unset($Block['interrupted']);
329 $Block['element']['text']['text'] .= "\n";
331 $text = substr($Line['body'], 4);
333 $Block['element']['text']['text'] .= $text;
339 protected function blockCodeComplete($Block)
341 $text = $Block['element']['text']['text'];
343 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
345 $Block['element']['text']['text'] = $text;
353 protected function blockComment($Line)
355 if ($this->markupEscaped)
360 if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
363 'markup' => $Line['body'],
366 if (preg_match('/-->$/', $Line['text']))
368 $Block['closed'] = true;
375 protected function blockCommentContinue($Line, array $Block)
377 if (isset($Block['closed']))
382 $Block['markup'] .= "\n" . $Line['body'];
384 if (preg_match('/-->$/', $Line['text']))
386 $Block['closed'] = true;
395 protected function blockFencedCode($Line)
397 if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
404 if (isset($matches[2]))
406 $class = 'language-'.$matches[2];
408 $Element['attributes'] = array(
414 'char' => $Line['text'][0],
417 'handler' => 'element',
426 protected function blockFencedCodeContinue($Line, $Block)
428 if (isset($Block['complete']))
433 if (isset($Block['interrupted']))
435 $Block['element']['text']['text'] .= "\n";
437 unset($Block['interrupted']);
440 if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
442 $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
444 $Block['complete'] = true;
449 $Block['element']['text']['text'] .= "\n".$Line['body'];;
454 protected function blockFencedCodeComplete($Block)
456 $text = $Block['element']['text']['text'];
458 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
460 $Block['element']['text']['text'] = $text;
468 protected function blockHeader($Line)
470 if (isset($Line['text'][1]))
474 while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
484 $text = trim($Line['text'], '# ');
488 'name' => 'h' . min(6, $level),
501 protected function blockList($Line)
503 list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
505 if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
508 'indent' => $Line['indent'],
509 'pattern' => $pattern,
512 'handler' => 'elements',
516 $Block['li'] = array(
524 $Block['element']['text'] []= & $Block['li'];
530 protected function blockListContinue($Line, array $Block)
532 if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
534 if (isset($Block['interrupted']))
536 $Block['li']['text'] []= '';
538 unset($Block['interrupted']);
543 $text = isset($matches[1]) ? $matches[1] : '';
545 $Block['li'] = array(
553 $Block['element']['text'] []= & $Block['li'];
558 if ($Line['text'][0] === '[' and $this->blockReference($Line))
563 if ( ! isset($Block['interrupted']))
565 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
567 $Block['li']['text'] []= $text;
572 if ($Line['indent'] > 0)
574 $Block['li']['text'] []= '';
576 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
578 $Block['li']['text'] []= $text;
580 unset($Block['interrupted']);
589 protected function blockQuote($Line)
591 if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
595 'name' => 'blockquote',
596 'handler' => 'lines',
597 'text' => (array) $matches[1],
605 protected function blockQuoteContinue($Line, array $Block)
607 if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
609 if (isset($Block['interrupted']))
611 $Block['element']['text'] []= '';
613 unset($Block['interrupted']);
616 $Block['element']['text'] []= $matches[1];
621 if ( ! isset($Block['interrupted']))
623 $Block['element']['text'] []= $Line['text'];
632 protected function blockRule($Line)
634 if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
649 protected function blockSetextHeader($Line, array $Block = null)
651 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
656 if (chop($Line['text'], $Line['text'][0]) === '')
658 $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
667 protected function blockMarkup($Line)
669 if ($this->markupEscaped)
674 if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
676 if (in_array($matches[1], $this->textLevelElements))
682 'name' => $matches[1],
684 'markup' => $Line['text'],
687 $length = strlen($matches[0]);
689 $remainder = substr($Line['text'], $length);
691 if (trim($remainder) === '')
693 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
695 $Block['closed'] = true;
697 $Block['void'] = true;
702 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
707 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
709 $Block['closed'] = true;
717 protected function blockMarkupContinue($Line, array $Block)
719 if (isset($Block['closed']))
724 if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
729 if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
731 if ($Block['depth'] > 0)
737 $Block['closed'] = true;
741 if (isset($Block['interrupted']))
743 $Block['markup'] .= "\n";
745 unset($Block['interrupted']);
748 $Block['markup'] .= "\n".$Line['body'];
756 protected function blockReference($Line)
758 if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
760 $id = strtolower($matches[1]);
763 'url' => $matches[2],
767 if (isset($matches[3]))
769 $Data['title'] = $matches[3];
772 $this->DefinitionData['Reference'][$id] = $Data;
785 protected function blockTable($Line, array $Block = null)
787 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
792 if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
794 $alignments = array();
796 $divider = $Line['text'];
798 $divider = trim($divider);
799 $divider = trim($divider, '|');
801 $dividerCells = explode('|', $divider);
803 foreach ($dividerCells as $dividerCell)
805 $dividerCell = trim($dividerCell);
807 if ($dividerCell === '')
814 if ($dividerCell[0] === ':')
819 if (substr($dividerCell, - 1) === ':')
821 $alignment = $alignment === 'left' ? 'center' : 'right';
824 $alignments []= $alignment;
829 $HeaderElements = array();
831 $header = $Block['element']['text'];
833 $header = trim($header);
834 $header = trim($header, '|');
836 $headerCells = explode('|', $header);
838 foreach ($headerCells as $index => $headerCell)
840 $headerCell = trim($headerCell);
842 $HeaderElement = array(
844 'text' => $headerCell,
848 if (isset($alignments[$index]))
850 $alignment = $alignments[$index];
852 $HeaderElement['attributes'] = array(
853 'style' => 'text-align: '.$alignment.';',
857 $HeaderElements []= $HeaderElement;
863 'alignments' => $alignments,
864 'identified' => true,
867 'handler' => 'elements',
871 $Block['element']['text'] []= array(
873 'handler' => 'elements',
876 $Block['element']['text'] []= array(
878 'handler' => 'elements',
882 $Block['element']['text'][0]['text'] []= array(
884 'handler' => 'elements',
885 'text' => $HeaderElements,
892 protected function blockTableContinue($Line, array $Block)
894 if (isset($Block['interrupted']))
899 if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
903 $row = $Line['text'];
906 $row = trim($row, '|');
908 preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
910 foreach ($matches[0] as $index => $cell)
920 if (isset($Block['alignments'][$index]))
922 $Element['attributes'] = array(
923 'style' => 'text-align: '.$Block['alignments'][$index].';',
927 $Elements []= $Element;
932 'handler' => 'elements',
936 $Block['element']['text'][1]['text'] []= $Element;
946 protected function paragraph($Line)
951 'text' => $Line['text'],
963 protected $InlineTypes = array(
964 '"' => array('SpecialCharacter'),
965 '!' => array('Image'),
966 '&' => array('SpecialCharacter'),
967 '*' => array('Emphasis'),
969 '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
970 '>' => array('SpecialCharacter'),
971 '[' => array('Link'),
972 '_' => array('Emphasis'),
973 '`' => array('Code'),
974 '~' => array('Strikethrough'),
975 '\\' => array('EscapeSequence'),
980 protected $inlineMarkerList = '!"*_&[:<>`~\\';
986 public function line($text)
990 $unexaminedText = $text;
994 while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
996 $marker = $excerpt[0];
998 $markerPosition += strpos($unexaminedText, $marker);
1000 $Excerpt = array('text' => $excerpt, 'context' => $text);
1002 foreach ($this->InlineTypes[$marker] as $inlineType)
1004 $Inline = $this->{'inline'.$inlineType}($Excerpt);
1006 if ( ! isset($Inline))
1011 if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker
1016 if ( ! isset($Inline['position']))
1018 $Inline['position'] = $markerPosition;
1021 $unmarkedText = substr($text, 0, $Inline['position']);
1023 $markup .= $this->unmarkedText($unmarkedText);
1025 $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1027 $text = substr($text, $Inline['position'] + $Inline['extent']);
1029 $unexaminedText = $text;
1031 $markerPosition = 0;
1036 $unexaminedText = substr($excerpt, 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 = $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 self();
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',