Release of v4.1.1-alpha1

Move all banners to GitHub. Adds library phpspreadsheet to JCB. Add import item example to demo component. Updates the Superpower class with the GetRemote class in the plugin. Ensures the super power autoloader triggers the correct repositories.
This commit is contained in:
2025-03-04 21:53:04 +00:00
parent 8549348a61
commit d11860ae1a
1141 changed files with 193033 additions and 158 deletions

View File

@ -0,0 +1,337 @@
<?php
/**
* Parser that uses PHP 5's DOM extension (part of the core).
*
* In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
* It gives us a forgiving HTML parser, which we use to transform the HTML
* into a DOM, and then into the tokens. It is blazingly fast (for large
* documents, it performs twenty times faster than
* HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
*
* @note Any empty elements will have empty tokens associated with them, even if
* this is prohibited by the spec. This is cannot be fixed until the spec
* comes into play.
*
* @note PHP's DOM extension does not actually parse any entities, we use
* our own function to do that.
*
* @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
* If this is a huge problem, due to the fact that HTML is hand
* edited and you are unable to get a parser cache that caches the
* the output of HTML Purifier while keeping the original HTML lying
* around, you may want to run Tidy on the resulting output or use
* HTMLPurifier_DirectLex
*/
class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
{
/**
* @type HTMLPurifier_TokenFactory
*/
private $factory;
public function __construct()
{
// setup the factory
parent::__construct();
$this->factory = new HTMLPurifier_TokenFactory();
}
/**
* @param string $html
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return HTMLPurifier_Token[]
*/
public function tokenizeHTML($html, $config, $context)
{
$html = $this->normalize($html, $config, $context);
// attempt to armor stray angled brackets that cannot possibly
// form tags and thus are probably being used as emoticons
if ($config->get('Core.AggressivelyFixLt')) {
$char = '[^a-z!\/]';
$comment = "/<!--(.*?)(-->|\z)/is";
$html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
do {
$old = $html;
$html = preg_replace("/<($char)/i", '&lt;\\1', $html);
} while ($html !== $old);
$html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
}
// preprocess html, essential for UTF-8
$html = $this->wrapHTML($html, $config, $context);
$doc = new DOMDocument();
$doc->encoding = 'UTF-8'; // theoretically, the above has this covered
$options = 0;
if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {
$options |= LIBXML_PARSEHUGE;
}
set_error_handler(array($this, 'muteErrorHandler'));
// loadHTML() fails on PHP 5.3 when second parameter is given
if ($options) {
$doc->loadHTML($html, $options);
} else {
$doc->loadHTML($html);
}
restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0); // <body>
$div = $body->getElementsByTagName('div')->item(0); // <div>
$tokens = array();
$this->tokenizeDOM($div, $tokens, $config);
// If the div has a sibling, that means we tripped across
// a premature </div> tag. So remove the div we parsed,
// and then tokenize the rest of body. We can't tokenize
// the sibling directly as we'll lose the tags in that case.
if ($div->nextSibling) {
$body->removeChild($div);
$this->tokenizeDOM($body, $tokens, $config);
}
return $tokens;
}
/**
* Iterative function that tokenizes a node, putting it into an accumulator.
* To iterate is human, to recurse divine - L. Peter Deutsch
* @param DOMNode $node DOMNode to be tokenized.
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
*/
protected function tokenizeDOM($node, &$tokens, $config)
{
$level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node)));
$closingNodes = array();
do {
while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO
$collect = $level > 0 ? true : false;
$needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
if ($needEndingTag) {
$closingNodes[$level][] = $node;
}
if ($node->childNodes && $node->childNodes->length) {
$level++;
$nodes[$level] = new HTMLPurifier_Queue();
foreach ($node->childNodes as $childNode) {
$nodes[$level]->push($childNode);
}
}
}
$level--;
if ($level && isset($closingNodes[$level])) {
while ($node = array_pop($closingNodes[$level])) {
$this->createEndNode($node, $tokens);
}
}
} while ($level > 0);
}
/**
* Portably retrieve the tag name of a node; deals with older versions
* of libxml like 2.7.6
* @param DOMNode $node
*/
protected function getTagName($node)
{
if (isset($node->tagName)) {
return $node->tagName;
} else if (isset($node->nodeName)) {
return $node->nodeName;
} else if (isset($node->localName)) {
return $node->localName;
}
return null;
}
/**
* Portably retrieve the data of a node; deals with older versions
* of libxml like 2.7.6
* @param DOMNode $node
*/
protected function getData($node)
{
if (isset($node->data)) {
return $node->data;
} else if (isset($node->nodeValue)) {
return $node->nodeValue;
} else if (isset($node->textContent)) {
return $node->textContent;
}
return null;
}
/**
* @param DOMNode $node DOMNode to be tokenized.
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
* @param bool $collect Says whether or start and close are collected, set to
* false at first recursion because it's the implicit DIV
* tag you're dealing with.
* @return bool if the token needs an endtoken
* @todo data and tagName properties don't seem to exist in DOMNode?
*/
protected function createStartNode($node, &$tokens, $collect, $config)
{
// intercept non element nodes. WE MUST catch all of them,
// but we're not getting the character reference nodes because
// those should have been preprocessed
if ($node->nodeType === XML_TEXT_NODE) {
$data = $this->getData($node); // Handle variable data property
if ($data !== null) {
$tokens[] = $this->factory->createText($data);
}
return false;
} elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
// undo libxml's special treatment of <script> and <style> tags
$last = end($tokens);
$data = $node->data;
// (note $node->tagname is already normalized)
if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
$new_data = trim($data);
if (substr($new_data, 0, 4) === '<!--') {
$data = substr($new_data, 4);
if (substr($data, -3) === '-->') {
$data = substr($data, 0, -3);
} else {
// Highly suspicious! Not sure what to do...
}
}
}
$tokens[] = $this->factory->createText($this->parseText($data, $config));
return false;
} elseif ($node->nodeType === XML_COMMENT_NODE) {
// this is code is only invoked for comments in script/style in versions
// of libxml pre-2.6.28 (regular comments, of course, are still
// handled regularly)
$tokens[] = $this->factory->createComment($node->data);
return false;
} elseif ($node->nodeType !== XML_ELEMENT_NODE) {
// not-well tested: there may be other nodes we have to grab
return false;
}
$attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
$tag_name = $this->getTagName($node); // Handle variable tagName property
if (empty($tag_name)) {
return (bool) $node->childNodes->length;
}
// We still have to make sure that the element actually IS empty
if (!$node->childNodes->length) {
if ($collect) {
$tokens[] = $this->factory->createEmpty($tag_name, $attr);
}
return false;
} else {
if ($collect) {
$tokens[] = $this->factory->createStart($tag_name, $attr);
}
return true;
}
}
/**
* @param DOMNode $node
* @param HTMLPurifier_Token[] $tokens
*/
protected function createEndNode($node, &$tokens)
{
$tag_name = $this->getTagName($node); // Handle variable tagName property
$tokens[] = $this->factory->createEnd($tag_name);
}
/**
* Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
*
* @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
* @return array Associative array of attributes.
*/
protected function transformAttrToAssoc($node_map)
{
// NamedNodeMap is documented very well, so we're using undocumented
// features, namely, the fact that it implements Iterator and
// has a ->length attribute
if ($node_map->length === 0) {
return array();
}
$array = array();
foreach ($node_map as $attr) {
$array[$attr->name] = $attr->value;
}
return $array;
}
/**
* An error handler that mutes all errors
* @param int $errno
* @param string $errstr
*/
public function muteErrorHandler($errno, $errstr)
{
}
/**
* Callback function for undoing escaping of stray angled brackets
* in comments
* @param array $matches
* @return string
*/
public function callbackUndoCommentSubst($matches)
{
return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
}
/**
* Callback function that entity-izes ampersands in comments so that
* callbackUndoCommentSubst doesn't clobber them
* @param array $matches
* @return string
*/
public function callbackArmorCommentEntities($matches)
{
return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
}
/**
* Wraps an HTML fragment in the necessary HTML
* @param string $html
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return string
*/
protected function wrapHTML($html, $config, $context, $use_div = true)
{
$def = $config->getDefinition('HTML');
$ret = '';
if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
$ret .= '<!DOCTYPE html ';
if (!empty($def->doctype->dtdPublic)) {
$ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
}
if (!empty($def->doctype->dtdSystem)) {
$ret .= '"' . $def->doctype->dtdSystem . '" ';
}
$ret .= '>';
}
$ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
// No protection if $html contains a stray </div>!
$ret .= '</head><body>';
if ($use_div) $ret .= '<div>';
$ret .= $html;
if ($use_div) $ret .= '</div>';
$ret .= '</body></html>';
return $ret;
}
}
// vim: et sw=4 sts=4

View File

@ -0,0 +1,539 @@
<?php
/**
* Our in-house implementation of a parser.
*
* A pure PHP parser, DirectLex has absolutely no dependencies, making
* it a reasonably good default for PHP4. Written with efficiency in mind,
* it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
* pales in comparison to HTMLPurifier_Lexer_DOMLex.
*
* @todo Reread XML spec and document differences.
*/
class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
{
/**
* @type bool
*/
public $tracksLineNumbers = true;
/**
* Whitespace characters for str(c)spn.
* @type string
*/
protected $_whitespace = "\x20\x09\x0D\x0A";
/**
* Callback function for script CDATA fudge
* @param array $matches, in form of array(opening tag, contents, closing tag)
* @return string
*/
protected function scriptCallback($matches)
{
return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
}
/**
* @param String $html
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array|HTMLPurifier_Token[]
*/
public function tokenizeHTML($html, $config, $context)
{
// special normalization for script tags without any armor
// our "armor" heurstic is a < sign any number of whitespaces after
// the first script tag
if ($config->get('HTML.Trusted')) {
$html = preg_replace_callback(
'#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
array($this, 'scriptCallback'),
$html
);
}
$html = $this->normalize($html, $config, $context);
$cursor = 0; // our location in the text
$inside_tag = false; // whether or not we're parsing the inside of a tag
$array = array(); // result array
// This is also treated to mean maintain *column* numbers too
$maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
if ($maintain_line_numbers === null) {
// automatically determine line numbering by checking
// if error collection is on
$maintain_line_numbers = $config->get('Core.CollectErrors');
}
if ($maintain_line_numbers) {
$current_line = 1;
$current_col = 0;
$length = strlen($html);
} else {
$current_line = false;
$current_col = false;
$length = false;
}
$context->register('CurrentLine', $current_line);
$context->register('CurrentCol', $current_col);
$nl = "\n";
// how often to manually recalculate. This will ALWAYS be right,
// but it's pretty wasteful. Set to 0 to turn off
$synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
$e = false;
if ($config->get('Core.CollectErrors')) {
$e =& $context->get('ErrorCollector');
}
// for testing synchronization
$loops = 0;
while (++$loops) {
// $cursor is either at the start of a token, or inside of
// a tag (i.e. there was a < immediately before it), as indicated
// by $inside_tag
if ($maintain_line_numbers) {
// $rcursor, however, is always at the start of a token.
$rcursor = $cursor - (int)$inside_tag;
// Column number is cheap, so we calculate it every round.
// We're interested at the *end* of the newline string, so
// we need to add strlen($nl) == 1 to $nl_pos before subtracting it
// from our "rcursor" position.
$nl_pos = strrpos($html, $nl, $rcursor - $length);
$current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
// recalculate lines
if ($synchronize_interval && // synchronization is on
$cursor > 0 && // cursor is further than zero
$loops % $synchronize_interval === 0) { // time to synchronize!
$current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
}
}
$position_next_lt = strpos($html, '<', $cursor);
$position_next_gt = strpos($html, '>', $cursor);
// triggers on "<b>asdf</b>" but not "asdf <b></b>"
// special case to set up context
if ($position_next_lt === $cursor) {
$inside_tag = true;
$cursor++;
}
if (!$inside_tag && $position_next_lt !== false) {
// We are not inside tag and there still is another tag to parse
$token = new
HTMLPurifier_Token_Text(
$this->parseText(
substr(
$html,
$cursor,
$position_next_lt - $cursor
), $config
)
);
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
}
$array[] = $token;
$cursor = $position_next_lt + 1;
$inside_tag = true;
continue;
} elseif (!$inside_tag) {
// We are not inside tag but there are no more tags
// If we're already at the end, break
if ($cursor === strlen($html)) {
break;
}
// Create Text of rest of string
$token = new
HTMLPurifier_Token_Text(
$this->parseText(
substr(
$html,
$cursor
), $config
)
);
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
}
$array[] = $token;
break;
} elseif ($inside_tag && $position_next_gt !== false) {
// We are in tag and it is well formed
// Grab the internals of the tag
$strlen_segment = $position_next_gt - $cursor;
if ($strlen_segment < 1) {
// there's nothing to process!
$token = new HTMLPurifier_Token_Text('<');
$cursor++;
continue;
}
$segment = substr($html, $cursor, $strlen_segment);
if ($segment === false) {
// somehow, we attempted to access beyond the end of
// the string, defense-in-depth, reported by Nate Abele
break;
}
// Check if it's a comment
if (substr($segment, 0, 3) === '!--') {
// re-determine segment length, looking for -->
$position_comment_end = strpos($html, '-->', $cursor);
if ($position_comment_end === false) {
// uh oh, we have a comment that extends to
// infinity. Can't be helped: set comment
// end position to end of string
if ($e) {
$e->send(E_WARNING, 'Lexer: Unclosed comment');
}
$position_comment_end = strlen($html);
$end = true;
} else {
$end = false;
}
$strlen_segment = $position_comment_end - $cursor;
$segment = substr($html, $cursor, $strlen_segment);
$token = new
HTMLPurifier_Token_Comment(
substr(
$segment,
3,
$strlen_segment - 3
)
);
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
}
$array[] = $token;
$cursor = $end ? $position_comment_end : $position_comment_end + 3;
$inside_tag = false;
continue;
}
// Check if it's an end tag
$is_end_tag = (strpos($segment, '/') === 0);
if ($is_end_tag) {
$type = substr($segment, 1);
$token = new HTMLPurifier_Token_End($type);
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
}
$array[] = $token;
$inside_tag = false;
$cursor = $position_next_gt + 1;
continue;
}
// Check leading character is alnum, if not, we may
// have accidently grabbed an emoticon. Translate into
// text and go our merry way
if (!ctype_alpha($segment[0])) {
// XML: $segment[0] !== '_' && $segment[0] !== ':'
if ($e) {
$e->send(E_NOTICE, 'Lexer: Unescaped lt');
}
$token = new HTMLPurifier_Token_Text('<');
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
}
$array[] = $token;
$inside_tag = false;
continue;
}
// Check if it is explicitly self closing, if so, remove
// trailing slash. Remember, we could have a tag like <br>, so
// any later token processing scripts must convert improperly
// classified EmptyTags from StartTags.
$is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
if ($is_self_closing) {
$strlen_segment--;
$segment = substr($segment, 0, $strlen_segment);
}
// Check if there are any attributes
$position_first_space = strcspn($segment, $this->_whitespace);
if ($position_first_space >= $strlen_segment) {
if ($is_self_closing) {
$token = new HTMLPurifier_Token_Empty($segment);
} else {
$token = new HTMLPurifier_Token_Start($segment);
}
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
}
$array[] = $token;
$inside_tag = false;
$cursor = $position_next_gt + 1;
continue;
}
// Grab out all the data
$type = substr($segment, 0, $position_first_space);
$attribute_string =
trim(
substr(
$segment,
$position_first_space
)
);
if ($attribute_string) {
$attr = $this->parseAttributeString(
$attribute_string,
$config,
$context
);
} else {
$attr = array();
}
if ($is_self_closing) {
$token = new HTMLPurifier_Token_Empty($type, $attr);
} else {
$token = new HTMLPurifier_Token_Start($type, $attr);
}
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
}
$array[] = $token;
$cursor = $position_next_gt + 1;
$inside_tag = false;
continue;
} else {
// inside tag, but there's no ending > sign
if ($e) {
$e->send(E_WARNING, 'Lexer: Missing gt');
}
$token = new
HTMLPurifier_Token_Text(
'<' .
$this->parseText(
substr($html, $cursor), $config
)
);
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
}
// no cursor scroll? Hmm...
$array[] = $token;
break;
}
break;
}
$context->destroy('CurrentLine');
$context->destroy('CurrentCol');
return $array;
}
/**
* PHP 5.0.x compatible substr_count that implements offset and length
* @param string $haystack
* @param string $needle
* @param int $offset
* @param int $length
* @return int
*/
protected function substrCount($haystack, $needle, $offset, $length)
{
static $oldVersion;
if ($oldVersion === null) {
$oldVersion = version_compare(PHP_VERSION, '5.1', '<');
}
if ($oldVersion) {
$haystack = substr($haystack, $offset, $length);
return substr_count($haystack, $needle);
} else {
return substr_count($haystack, $needle, $offset, $length);
}
}
/**
* Takes the inside of an HTML tag and makes an assoc array of attributes.
*
* @param string $string Inside of tag excluding name.
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array Assoc array of attributes.
*/
public function parseAttributeString($string, $config, $context)
{
$string = (string)$string; // quick typecast
if ($string == '') {
return array();
} // no attributes
$e = false;
if ($config->get('Core.CollectErrors')) {
$e =& $context->get('ErrorCollector');
}
// let's see if we can abort as quickly as possible
// one equal sign, no spaces => one attribute
$num_equal = substr_count($string, '=');
$has_space = strpos($string, ' ');
if ($num_equal === 0 && !$has_space) {
// bool attribute
return array($string => $string);
} elseif ($num_equal === 1 && !$has_space) {
// only one attribute
list($key, $quoted_value) = explode('=', $string);
$quoted_value = trim($quoted_value);
if (!$key) {
if ($e) {
$e->send(E_ERROR, 'Lexer: Missing attribute key');
}
return array();
}
if (!$quoted_value) {
return array($key => '');
}
$first_char = @$quoted_value[0];
$last_char = @$quoted_value[strlen($quoted_value) - 1];
$same_quote = ($first_char == $last_char);
$open_quote = ($first_char == '"' || $first_char == "'");
if ($same_quote && $open_quote) {
// well behaved
$value = substr($quoted_value, 1, strlen($quoted_value) - 2);
} else {
// not well behaved
if ($open_quote) {
if ($e) {
$e->send(E_ERROR, 'Lexer: Missing end quote');
}
$value = substr($quoted_value, 1);
} else {
$value = $quoted_value;
}
}
if ($value === false) {
$value = '';
}
return array($key => $this->parseAttr($value, $config));
}
// setup loop environment
$array = array(); // return assoc array of attributes
$cursor = 0; // current position in string (moves forward)
$size = strlen($string); // size of the string (stays the same)
// if we have unquoted attributes, the parser expects a terminating
// space, so let's guarantee that there's always a terminating space.
$string .= ' ';
$old_cursor = -1;
while ($cursor < $size) {
if ($old_cursor >= $cursor) {
throw new Exception("Infinite loop detected");
}
$old_cursor = $cursor;
$cursor += ($value = strspn($string, $this->_whitespace, $cursor));
// grab the key
$key_begin = $cursor; //we're currently at the start of the key
// scroll past all characters that are the key (not whitespace or =)
$cursor += strcspn($string, $this->_whitespace . '=', $cursor);
$key_end = $cursor; // now at the end of the key
$key = substr($string, $key_begin, $key_end - $key_begin);
if (!$key) {
if ($e) {
$e->send(E_ERROR, 'Lexer: Missing attribute key');
}
$cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
continue; // empty key
}
// scroll past all whitespace
$cursor += strspn($string, $this->_whitespace, $cursor);
if ($cursor >= $size) {
$array[$key] = $key;
break;
}
// if the next character is an equal sign, we've got a regular
// pair, otherwise, it's a bool attribute
$first_char = @$string[$cursor];
if ($first_char == '=') {
// key="value"
$cursor++;
$cursor += strspn($string, $this->_whitespace, $cursor);
if ($cursor === false) {
$array[$key] = '';
break;
}
// we might be in front of a quote right now
$char = @$string[$cursor];
if ($char == '"' || $char == "'") {
// it's quoted, end bound is $char
$cursor++;
$value_begin = $cursor;
$cursor = strpos($string, $char, $cursor);
$value_end = $cursor;
} else {
// it's not quoted, end bound is whitespace
$value_begin = $cursor;
$cursor += strcspn($string, $this->_whitespace, $cursor);
$value_end = $cursor;
}
// we reached a premature end
if ($cursor === false) {
$cursor = $size;
$value_end = $cursor;
}
$value = substr($string, $value_begin, $value_end - $value_begin);
if ($value === false) {
$value = '';
}
$array[$key] = $this->parseAttr($value, $config);
$cursor++;
} else {
// boolattr
if ($key !== '') {
$array[$key] = $key;
} else {
// purely theoretical
if ($e) {
$e->send(E_ERROR, 'Lexer: Missing attribute key');
}
}
}
}
return $array;
}
}
// vim: et sw=4 sts=4

File diff suppressed because it is too large Load Diff