Completed
Push — master ( a0fa0b...06252b )
by Tom
02:58
created

PseudoMatcher::parseValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 5
rs 9.4286
cc 3
eloc 4
nc 3
nop 1
1
<?php
2
/* @description     Transformation Style Sheets - Revolutionising PHP templating    *
3
 * @author          Tom Butler [email protected]                                             *
4
 * @copyright       2015 Tom Butler <[email protected]> | https://r.je/                      *
5
 * @license         http://www.opensource.org/licenses/bsd-license.php  BSD License *
6
 * @version         1.0                                                             */
7
namespace Transphporm\Hook;
8
/** Determines whether $element matches the pseudo rule such as nth-child() or [attribute="value"] */
9
class PseudoMatcher {
10
	private $pseudo;
11
	private $dataFunction;
12
13
	public function __construct($pseudo, DataFunction $dataFunction) {
14
		$this->pseudo = $pseudo;
15
		$this->dataFunction = $dataFunction;
16
	}
17
18
	public function matches($element) {
19
		$matches = true;
20
		$functions = ['attribute', 'nth', 'not'];
21
22
		foreach ($this->pseudo as $pseudo) {			
23
			foreach ($functions as $func) {
24
				$matches = $matches && $this->$func($pseudo, $element);
25
			}
26
		}		
27
		return $matches;
28
	}
29
30
	private function betweenBrackets($string, $openChr, $closingChr, $start = 0) {
31
		$open = strpos($string, $openChr, $start);
32
		$close = strpos($string, $closingChr, $open);
33
34
		$cPos = $open+1;
35
		while (($cPos = strpos($string, $openChr, $cPos+1)) !== false && $cPos < $close) $close = strpos($string, $closingChr, $close+1);
36
37
		return substr($string, $open+1, $close-$open-1);
38
	}
39
	
40
	private function attribute($pseudo, $element) {
41
		$pos = strpos($pseudo, '[');
42
		if ($pos === false) return true;
43
		
44
		$name = substr($pseudo, 0, $pos);
45
		if (!is_callable([$this->dataFunction, $name])) return true;
46
47
		$criteria = $this->betweenBrackets($pseudo, '[', ']');
48
49
		if (strpos($pseudo, '=') === false) {
50
			$lookupValue = $this->dataFunction->$name([$criteria], $element);
51
			return $lookupValue !== null;
52
		}
53
		list ($field, $value) = explode('=', $criteria);
54
55
		$operator = $this->getOperator($field);
56
		$lookupValue = $this->dataFunction->$name([trim($field, $operator)], $element);
57
58
		return $this->processOperator($operator, $lookupValue, $this->parseValue(trim($value, '"')));
59
	}
60
61
	//Currently only not is supported, but this is separated out to support others in future
62
	private function processOperator($operator, $lookupValue, $value) {
63
		$matched = $lookupValue == $value;
64
		return $operator === '!' ? !$matched : $matched;
65
	}
66
67
	private function parseValue($value) {
68
		if ($value == 'true') return true;
69
		else if ($value == 'false') return false;
70
		else return $value;
71
	}
72
73
	private function getOperator($field) {
74
		if ($field[strlen($field)-1] == '!') {
75
			return '!';
76
		}
77
		else return '';
78
	}
79
80
	private function nth($pseudo, $element) {
81
		if (strpos($pseudo, 'nth-child') === 0) {	
82
			$criteria = $this->getBetween($pseudo, '(', ')');
83
			$num = $this->getBetween($element->getNodePath(), '[', ']');
84
			
85
			if (is_callable([$this, $criteria])) return $this->$criteria($num);
86
			else return $num == $criteria;
87
			
88
		}
89
		return true;
90
	}
91
92
	private function not($pseudo, $element) {
93
		if (strpos($pseudo, 'not') === 0) {
94
			$valueParser = new \Transphporm\Parser\Value($this->dataFunction);
95
			$css = explode(',', $this->getBetween($pseudo, '(', ')'));
96
97
			foreach ($css as $selector) {
98
				$cssToXpath = new \Transphporm\Parser\CssToXpath($selector, $valueParser);
99
				$xpathString = $cssToXpath->getXpath();	
100
				$xpath = new \DomXpath($element->ownerDocument);
101
				
102
				foreach ($xpath->query($xpathString) as $matchedElement) {
103
					if ($element->isSameNode($matchedElement)) return false;
104
				}
105
			}
106
		}
107
		return true;
108
	}
109
110
	public function attr() {
111
		foreach ($this->pseudo as $pseudo) {
112
			if (strpos($pseudo, 'attr') === 0) {
113
				$criteria = trim($this->getBetween($pseudo, '(', ')'));
114
				return $criteria;
115
			}
116
		}
117
118
		return false;
119
	}
120
121
	public function header($element)  {
122
		if ($this->matches($element)) {
123
			foreach ($this->pseudo as $pseudo) {
124
				if (strpos($pseudo, 'header') === 0) return $this->getBetween($pseudo, '[', ']');
125
			}
126
		}
127
	}
128
129
	private function odd($num) {
130
		return $num % 2 === 1;
131
	}
132
133
	private function even($num) {
134
		return $num % 2 === 0;
135
	}
136
137
	private function getBetween($string, $start, $end) {
138
		$open = strpos($string, $start);
139
		if ($open === false) return false;
140
		$close = strpos($string, $end, $open);
141
		return substr($string, $open+1, $close-$open-1);
142
	}
143
144
	public function getPseudo() {
145
		return $this->pseudo;
146
	}
147
}