Sort out namespaced attributes
This commit is contained in:
parent
02add5633a
commit
82621a11e3
5 changed files with 100 additions and 61 deletions
|
@ -68,7 +68,7 @@ class ParseError {
|
||||||
const UNEXPECTED_CHAR = 211;
|
const UNEXPECTED_CHAR = 211;
|
||||||
const UNEXPECTED_EOF = 212;
|
const UNEXPECTED_EOF = 212;
|
||||||
const UNEXPECTED_PARENT = 213;
|
const UNEXPECTED_PARENT = 213;
|
||||||
const UNEXPECTED_ATTRIBUTE_VALUE = 214;
|
const INVALID_NAMESPACE_ATTRIBUTE_VALUE = 214;
|
||||||
const FOSTERED_START_TAG = 215;
|
const FOSTERED_START_TAG = 215;
|
||||||
const FOSTERED_END_TAG = 216;
|
const FOSTERED_END_TAG = 216;
|
||||||
const FOSTERED_CHAR = 217;
|
const FOSTERED_CHAR = 217;
|
||||||
|
@ -87,7 +87,7 @@ class ParseError {
|
||||||
self::UNEXPECTED_CHAR => 'Unexpected character data',
|
self::UNEXPECTED_CHAR => 'Unexpected character data',
|
||||||
self::UNEXPECTED_EOF => 'Unexpected end of file',
|
self::UNEXPECTED_EOF => 'Unexpected end of file',
|
||||||
self::UNEXPECTED_PARENT => 'Start tag <%s> not valid in parent <%s>',
|
self::UNEXPECTED_PARENT => 'Start tag <%s> not valid in parent <%s>',
|
||||||
self::UNEXPECTED_ATTRIBUTE_VALUE => 'Unexpected value in attribute "%s"',
|
self::INVALID_NAMESPACE_ATTRIBUTE_VALUE => 'Invalid value for attribute "%s"; it must have value "%s" or be omitted',
|
||||||
self::FOSTERED_START_TAG => 'Start tag <%s> moved to before table',
|
self::FOSTERED_START_TAG => 'Start tag <%s> moved to before table',
|
||||||
self::FOSTERED_END_TAG => 'End tag </%s> moved to before table',
|
self::FOSTERED_END_TAG => 'End tag </%s> moved to before table',
|
||||||
self::FOSTERED_CHAR => 'Character moved to before table',
|
self::FOSTERED_CHAR => 'Character moved to before table',
|
||||||
|
|
|
@ -70,41 +70,34 @@ abstract class TagToken extends Token {
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAttribute(string $name) {
|
public function hasAttribute(string $name): bool {
|
||||||
$key = $this->_getAttributeKey($name);
|
return (!is_null($this->_getAttributeKey($name)));
|
||||||
|
}
|
||||||
|
|
||||||
return (isset($this->attributes[$key])) ? $this->attributes[$key] : null;
|
public function getAttribute(string $name) {
|
||||||
}
|
$key = $this->_getAttributeKey($name);
|
||||||
|
return (isset($this->attributes[$key])) ? $this->attributes[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasAttribute(string $name): bool {
|
public function setAttribute(string $name, string $value) {
|
||||||
return (!is_null($this->_getAttributeKey($name)));
|
$key = $this->_getAttributeKey($name);
|
||||||
}
|
if (is_null($key)) {
|
||||||
|
$this->attributes[] = new TokenAttr($name, $value);
|
||||||
|
} else {
|
||||||
|
$attribute = &$this->attributes[$key];
|
||||||
|
$attribute->name = $name;
|
||||||
|
$attribute->value = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function removeAttribute(string $name) {
|
private function _getAttributeKey(string $name) {
|
||||||
unset($this->attributes[$this->_getAttributeKey($name)]);
|
foreach ($this->attributes as $key => $a) {
|
||||||
}
|
if ($a->name === $name) {
|
||||||
|
return $key;
|
||||||
public function setAttribute(string $name, string $value) {
|
}
|
||||||
$key = $this->_getAttributeKey($name);
|
}
|
||||||
|
return null;
|
||||||
if (is_null($key)) {
|
}
|
||||||
$this->attributes[] = new TokenAttr($name, $value);
|
|
||||||
} else {
|
|
||||||
$attribute = &$this->attributes[$key];
|
|
||||||
$attribute->name = $name;
|
|
||||||
$attribute->value = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function _getAttributeKey(string $name) {
|
|
||||||
foreach ($this->attributes as $key => $a) {
|
|
||||||
if ($a->name === $name) {
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartTagToken extends TagToken {
|
class StartTagToken extends TagToken {
|
||||||
|
@ -120,8 +113,12 @@ class EOFToken extends Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TokenAttr {
|
class TokenAttr {
|
||||||
|
/** @var string The name of the attribute */
|
||||||
public $name;
|
public $name;
|
||||||
|
/** @var string The attribute's value */
|
||||||
public $value;
|
public $value;
|
||||||
|
/** @var string|null The attribute's namespace. This is normally null but may be set during tree construction */
|
||||||
|
public $namespace = null;
|
||||||
|
|
||||||
public function __construct(string $name, string $value) {
|
public function __construct(string $name, string $value) {
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
|
|
@ -192,11 +192,11 @@ class TreeBuilder {
|
||||||
'xlink:show' => Parser::XLINK_NAMESPACE,
|
'xlink:show' => Parser::XLINK_NAMESPACE,
|
||||||
'xlink:title' => Parser::XLINK_NAMESPACE,
|
'xlink:title' => Parser::XLINK_NAMESPACE,
|
||||||
'xlink:type' => Parser::XLINK_NAMESPACE,
|
'xlink:type' => Parser::XLINK_NAMESPACE,
|
||||||
'xml:base' => Parser::XML_NAMESPACE,
|
'xml:id' => Parser::XML_NAMESPACE, // DEVIATION
|
||||||
'xml:lang' => Parser::XML_NAMESPACE,
|
'xml:lang' => Parser::XML_NAMESPACE,
|
||||||
'xml:space' => Parser::XML_NAMESPACE,
|
'xml:space' => Parser::XML_NAMESPACE,
|
||||||
'xmlns' => Parser::XMLNS_NAMESPACE,
|
'xmlns' => Parser::XMLNS_NAMESPACE,
|
||||||
'xmlns:xlink' => Parser::XLINK_NAMESPACE,
|
'xmlns:xlink' => Parser::XMLNS_NAMESPACE,
|
||||||
];
|
];
|
||||||
# The following elements have varying levels of special parsing rules: HTML’s
|
# The following elements have varying levels of special parsing rules: HTML’s
|
||||||
# address, applet, area, article, aside, base, basefont, bgsound, blockquote,
|
# address, applet, area, article, aside, base, basefont, bgsound, blockquote,
|
||||||
|
@ -4277,29 +4277,21 @@ class TreeBuilder {
|
||||||
$element = $document->createElementNS($namespace, $localName);
|
$element = $document->createElementNS($namespace, $localName);
|
||||||
# Append each attribute in the given token to element.
|
# Append each attribute in the given token to element.
|
||||||
foreach ($token->attributes as $attr) {
|
foreach ($token->attributes as $attr) {
|
||||||
$ns = null;
|
# If element has an xmlns attribute in the XMLNS namespace whose value
|
||||||
if ($namespace) {
|
# is not exactly the same as the element's namespace, that is a
|
||||||
// Determine the namespace URI for the prefix, if any
|
# parse error. Similarly, if element has an xmlns:xlink attribute in
|
||||||
if (strpos($attr->name, "xml:") === 0) {
|
# the XMLNS namespace whose value is not the XLink Namespace, that
|
||||||
$ns = Parser::XML_NAMESPACE;
|
# is a parse error.
|
||||||
} elseif (strpos($attr->name, "xmlns:") === 0) {
|
// NOTE: The specification is silent as to how to handle these
|
||||||
$ns = Parser::XMLNS_NAMESPACE;
|
// attributes. We assume these bad attributes should be dropped,
|
||||||
} elseif (strpos($attr->name, "xlink:") === 0) {
|
// since they break the DOM when added
|
||||||
$ns = Parser::XLINK_NAMESPACE;
|
if ($attr->name === "xmlns" && $namespace !== null && $attr->value !== $namespace) {
|
||||||
}
|
$this->error(ParseError::INVALID_NAMESPACE_ATTRIBUTE_VALUE, "xmlns", $namespace);
|
||||||
|
} elseif ($attr->name === "xmlns:xlink" && $namespace !== null && $attr->value !== Parser::XLINK_NAMESPACE) {
|
||||||
|
$this->error(ParseError::INVALID_NAMESPACE_ATTRIBUTE_VALUE, "xmlns:xlink", Parser::XLINK_NAMESPACE);
|
||||||
|
} else {
|
||||||
|
$element->setAttributeNS($attr->namespace, $attr->name, $attr->value);
|
||||||
}
|
}
|
||||||
$element->setAttributeNS($ns, $attr->name, $attr->value);
|
|
||||||
}
|
|
||||||
# If element has an xmlns attribute in the XMLNS namespace whose value
|
|
||||||
# is not exactly the same as the element's namespace, that is a
|
|
||||||
# parse error. Similarly, if element has an xmlns:xlink attribute in
|
|
||||||
# the XMLNS namespace whose value is not the XLink Namespace, that
|
|
||||||
# is a parse error.
|
|
||||||
if ($element->hasAttributeNS(Parser::XMLNS_NAMESPACE, "xmlns") && $element->getAttributeNS(Parser::XMLNS_NAMESPACE, "xmlns") !== $element->namespaceURI) {
|
|
||||||
$this->error(ParseError::UNEXPECTED_ATTRIBUTE_VALUE, "xmlns");
|
|
||||||
}
|
|
||||||
if ($element->hasAttributeNS(Parser::XMLNS_NAMESPACE, "xmlns:link") && $element->getAttributeNS(Parser::XMLNS_NAMESPACE, "xmlns:xlink") !== Parser::XLINK_NAMESPACE) {
|
|
||||||
$this->error(ParseError::UNEXPECTED_ATTRIBUTE_VALUE, "xmlns:xlink");
|
|
||||||
}
|
}
|
||||||
# Return element.
|
# Return element.
|
||||||
return $element;
|
return $element;
|
||||||
|
|
|
@ -76,9 +76,6 @@ class TestTreeConstructor extends \PHPUnit\Framework\TestCase {
|
||||||
// run the tree builder
|
// run the tree builder
|
||||||
try {
|
try {
|
||||||
$treeBuilder->constructTree();
|
$treeBuilder->constructTree();
|
||||||
} catch (\DOMException $e) {
|
|
||||||
$this->markTestIncomplete('Requires implementation of the "Coercing an HTML DOM into an infoset" specification section');
|
|
||||||
return;
|
|
||||||
} catch (LoopException $e) {
|
} catch (LoopException $e) {
|
||||||
$act = $this->balanceTree($this->serializeTree($doc, (bool) $fragmentContext), $exp);
|
$act = $this->balanceTree($this->serializeTree($doc, (bool) $fragmentContext), $exp);
|
||||||
$this->assertEquals($exp, $act, $e->getMessage()."\n".$treeBuilder->debugLog);
|
$this->assertEquals($exp, $act, $e->getMessage()."\n".$treeBuilder->debugLog);
|
||||||
|
|
53
tests/cases/tree-construction/mensbeam01.dat
Normal file
53
tests/cases/tree-construction/mensbeam01.dat
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#data
|
||||||
|
<!DOCTYPE html><svg xmlns="http://www.w3.org/2000/svg"/>
|
||||||
|
#errors
|
||||||
|
#document
|
||||||
|
| <!DOCTYPE html>
|
||||||
|
| <html>
|
||||||
|
| <head>
|
||||||
|
| <body>
|
||||||
|
| <svg svg>
|
||||||
|
|
||||||
|
#data
|
||||||
|
<!DOCTYPE html><svg xmlns="http://www.w3.org/1999/xlink"/>
|
||||||
|
#errors
|
||||||
|
(1,58): invalid-namespace-attribute-value
|
||||||
|
#document
|
||||||
|
| <!DOCTYPE html>
|
||||||
|
| <html>
|
||||||
|
| <head>
|
||||||
|
| <body>
|
||||||
|
| <svg svg>
|
||||||
|
|
||||||
|
#data
|
||||||
|
<!DOCTYPE html><svg xmlns:xlink="http://www.w3.org/1999/xlink"/>
|
||||||
|
#errors
|
||||||
|
#document
|
||||||
|
| <!DOCTYPE html>
|
||||||
|
| <html>
|
||||||
|
| <head>
|
||||||
|
| <body>
|
||||||
|
| <svg svg>
|
||||||
|
|
||||||
|
#data
|
||||||
|
<!DOCTYPE html><svg xlink:href="http://example.com/"/>
|
||||||
|
#errors
|
||||||
|
#document
|
||||||
|
| <!DOCTYPE html>
|
||||||
|
| <html>
|
||||||
|
| <head>
|
||||||
|
| <body>
|
||||||
|
| <svg svg>
|
||||||
|
| xlink href="http://example.com/"
|
||||||
|
|
||||||
|
#data
|
||||||
|
<!DOCTYPE html><svg xmlns:xlink="http://www.w3.org/1999/xhtml" xlink:href="http://example.com/"/>
|
||||||
|
#errors
|
||||||
|
(1,97): invalid-namespace-attribute-value
|
||||||
|
#document
|
||||||
|
| <!DOCTYPE html>
|
||||||
|
| <html>
|
||||||
|
| <head>
|
||||||
|
| <body>
|
||||||
|
| <svg svg>
|
||||||
|
| xlink href="http://example.com/"
|
Loading…
Reference in a new issue