summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsmir Mustafic <[email protected]>2014-06-17 16:00:24 +0200
committerAsmir Mustafic <[email protected]>2014-06-17 16:00:24 +0200
commit03094ac9606d90265d8b6bcce908f739e9798e46 (patch)
tree8ed64d9d446b8bf8e042b6aa2fd07c5743efedc6
parent3959083837c7d2bfa6e922980b4a58faa68e5176 (diff)
Serialization rules for boolean attributes
-rw-r--r--src/HTML5/Serializer/OutputRules.php128
-rw-r--r--test/HTML5/Serializer/OutputRulesTest.php37
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()