diff options
author | Asmir Mustafic <[email protected]> | 2014-06-17 16:00:24 +0200 |
---|---|---|
committer | Asmir Mustafic <[email protected]> | 2014-06-17 16:00:24 +0200 |
commit | 03094ac9606d90265d8b6bcce908f739e9798e46 (patch) | |
tree | 8ed64d9d446b8bf8e042b6aa2fd07c5743efedc6 | |
parent | 3959083837c7d2bfa6e922980b4a58faa68e5176 (diff) |
Serialization rules for boolean attributes
-rw-r--r-- | src/HTML5/Serializer/OutputRules.php | 128 | ||||
-rw-r--r-- | test/HTML5/Serializer/OutputRulesTest.php | 37 |
2 files changed, 162 insertions, 3 deletions
diff --git a/src/HTML5/Serializer/OutputRules.php b/src/HTML5/Serializer/OutputRules.php index 2969383..ec3f2b9 100644 --- a/src/HTML5/Serializer/OutputRules.php +++ b/src/HTML5/Serializer/OutputRules.php @@ -30,6 +30,82 @@ class OutputRules implements \Masterminds\HTML5\Serializer\RulesInterface protected $outputMode; + private $xpath; + + protected $booleanAttributes = array( + /* + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'attrNamespace'=>'http://www.w3.org/1999/xhtml', + + 'nodeName'=>'select', + 'attrName'=>'multiple', + + 'prefixes'=>['xh'=>'http://www.w3.org/1999/xhtml'], + 'xpath' => "xh:option/@selected", + ), + */ + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'select', + 'attrName'=>'multiple', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'option', + 'attrName'=>'selected', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'script', + 'attrName'=>'defer', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>['input', 'textarea', 'button', 'select', 'option', 'optgroup'], + 'attrName'=>'disabled', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>['input', 'textarea', 'button', 'select', 'option', 'optgroup'], + 'attrName'=>'disabled', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'input', + 'attrName'=>'checked', + 'xpath' => "@checked[../../xh:input[@type='radio' or @type='checkbox']]", + 'prefixes'=>['xh'=>'http://www.w3.org/1999/xhtml'], + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>['input', 'textarea'], + 'attrName'=>'readonly', + 'xpath' => "@readonly[../../xh:input[@type='radio' or @type='checkbox']]|@readonly[../../xh:textarea]", + 'prefixes'=>['xh'=>'http://www.w3.org/1999/xhtml'], + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>['input', 'textarea'], + 'attrName'=>'readonly', + 'xpath' => "@readonly[../../xh:input[@type='radio' or @type='checkbox']]|@readonly[../../xh:textarea]", + 'prefixes'=>['xh'=>'http://www.w3.org/1999/xhtml'], + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'img', + 'attrName'=>'ismap', + ), + array( + 'nodeNamespace'=>'http://www.w3.org/1999/xhtml', + 'nodeName'=>'input', + 'attrName'=>'ismap', + 'xpath' => "@checked[../../xh:input[@type='image']]", + 'prefixes'=>['xh'=>'http://www.w3.org/1999/xhtml'], + ), + + ); + const DOCTYPE = '<!DOCTYPE html>'; public function __construct($output, $options = array()) @@ -41,6 +117,10 @@ class OutputRules implements \Masterminds\HTML5\Serializer\RulesInterface $this->outputMode = static::IM_IN_HTML; $this->out = $output; } + public function addRules(array $rule) + { + $this->booleanAttributes[] = $rule; + } public function setTraverser(\Masterminds\HTML5\Serializer\Traverser $traverser) { @@ -177,6 +257,7 @@ class OutputRules implements \Masterminds\HTML5\Serializer\RulesInterface // value-less attributes. $map = $ele->attributes; $len = $map->length; + for ($i = 0; $i < $len; ++ $i) { $node = $map->item($i); $val = $this->enc($node->value, true); @@ -199,12 +280,57 @@ class OutputRules implements \Masterminds\HTML5\Serializer\RulesInterface } $this->wr(' ')->wr($name); - if (isset($val) && $val !== '') { + + if (!$this->isBooleanAttribute($node)) { $this->wr('="')->wr($val)->wr('"'); } } } + protected function isBooleanAttribute(\DOMAttr $attr) + { + $ele = $attr->ownerElement; + foreach($this->booleanAttributes as $rule){ + + if(isset($rule['nodeNamespace']) && $rule['nodeNamespace']!==$ele->namespaceURI){ + continue; + } + if(isset($rule['nodeName']) && !is_array($rule['nodeName']) && $rule['nodeName']!==$ele->localName){ + continue; + } + if(isset($rule['nodeName']) && is_array($rule['nodeName']) && !in_array($ele->localName, $rule['nodeName'], true)){ + continue; + } + if(isset($rule['attNamespace']) && $rule['attNamespace']!==$attr->namespaceURI){ + continue; + } + if(isset($rule['attrName']) && $rule['attrName']!==$attr->localName){ + continue; + } + if(isset($rule['xpath'])){ + + $xp = $this->getXPath($attr); + if(isset($rule['prefixes'])){ + foreach($rule['prefixes'] as $nsPrefix => $ns){ + $xp->registerNamespace($nsPrefix, $ns); + } + } + if(!$xp->query($rule['xpath'], $attr->ownerElement)->length){ + continue; + } + } + return true; + } + return false; + } + + private function getXPath(\DOMNode $node){ + if(!$this->xpath){ + $this->xpath = new \DOMXPath($node->ownerDocument); + } + return $this->xpath; + } + /** * Write the closing tag. * diff --git a/test/HTML5/Serializer/OutputRulesTest.php b/test/HTML5/Serializer/OutputRulesTest.php index a54a754..9f1c573 100644 --- a/test/HTML5/Serializer/OutputRulesTest.php +++ b/test/HTML5/Serializer/OutputRulesTest.php @@ -438,12 +438,45 @@ class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute)); } + public function booleanAttributes() + { + return array( + array('<input type="radio" readonly>'), + array('<input type="radio" checked disabled>'), + array('<input type="checkbox" checked disabled>'), + array('<select disabled></select>'), + array('<img alt="">'), + array('<img alt="" ismap>'), + array('<script defer></script>'), + ); + } + /** + * @dataProvider booleanAttributes + */ + public function testBooleanAttrs($html) + { + $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>'.$html.'</body></html>'); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $node = $dom->getElementsByTagName('body')->item(0)->firstChild; + + $m = $this->getProtectedMethod('attrs'); + $m->invoke($r, $node); + + $content = stream_get_contents($stream, - 1, 0); + $this->assertContains($content, $html); + + } + public function testAttrs() { $dom = $this->html5->loadHTML('<!doctype html> <html lang="en"> <body> - <div id="foo" class="bar baz" disabled>foo bar baz</div> + <div id="foo" class="bar baz">foo bar baz</div> </body> </html>'); @@ -457,7 +490,7 @@ class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase $m->invoke($r, $list->item(0)); $content = stream_get_contents($stream, - 1, 0); - $this->assertEquals(' id="foo" class="bar baz" disabled', $content); + $this->assertEquals(' id="foo" class="bar baz"', $content); } public function testSvg() |