1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 
<?php

namespace SDom\SelectorMatcher;

use SDom\Node\Element;
use SDom\SelectorMatcher;
use Symfony\Component\CssSelector\Node\AttributeNode;
use Symfony\Component\CssSelector\Node\NodeInterface;

/**
 * @pattern E[foo]
 * @meaning an E element with a "foo" attribute
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo="bar"]
 * @meaning an E element whose "foo" attribute value is exactly equal to "bar"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo~="bar"]
 * @meaning an E element whose "foo" attribute value is a list of whitespace-separated values, one of which is
 * exactly equal to "bar"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo^="bar"]
 * @meaning an E element whose "foo" attribute value begins exactly with the string "bar"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo$="bar"]
 * @meaning an E element whose "foo" attribute value ends exactly with the string "bar"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo*="bar"]
 * @meaning an E element whose "foo" attribute value contains the substring "bar"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * @pattern E[foo|="en"]
 * @meaning an E element whose "foo" attribute has a hyphen-separated list of values beginning (from the left) with "en"
 * @link https://www.w3.org/TR/css3-selectors/#attribute-selectors
 *
 * Trait AttributeNodeTrait
 * @package SDom\SelectorMatcher
 */
trait AttributeNodeTrait
{
    /**
     * @param AttributeNode $token
     * @param Element $node
     * @param null|Element $effectiveRoot
     * @return bool
     */
    protected function matchAttributeNode(AttributeNode $token, Element $node, Element $effectiveRoot = null): bool
    {
        $attribute = $token->getAttribute();

        // node attribute must exist, regardless of operator
        if (!$node->hasAttribute($attribute)) {
            return false;
        }

        $neededValue = $token->getValue();
        $actualValue = $node->getAttribute($attribute);
        $operator = $token->getOperator();

        // if attribute operator is "exists", no further checks are needed
        if ('exists' === $operator) {
            return $this->match($token->getSelector(), $node, $effectiveRoot);
        }

        switch ($token->getOperator()) {
            case '=':
                if ($neededValue !== $actualValue) {
                    return false;
                }
                break;

            case '~=':
                if (!SelectorMatcher::containsWord($neededValue, $actualValue)) {
                    return false;
                }
                break;

            case '^=':
                if ($neededValue !== substr($actualValue, 0, strlen($neededValue))) {
                    return false;
                }
                break;

            case '$=':
                if ($neededValue !== substr($actualValue, -strlen($neededValue))) {
                    return false;
                }
                break;

            case '*=':
                if (false === strpos($actualValue, $neededValue)) {
                    return false;
                }
                break;

            case '|=':
                if (
                    $neededValue !== $actualValue &&
                    $neededValue . '-' !== substr($actualValue, 0, strlen($neededValue . '-'))
                ) {
                    return false;
                }
                break;

            default:
                throw new \RuntimeException(sprintf(
                    'Invalid node attribute operator "%s".',
                    $token->getOperator()
                ));
        }

        return $this->match($token->getSelector(), $node, $effectiveRoot);
    }

    /**
     * @param NodeInterface $token
     * @param Element $node
     * @param Element|null $effectiveRoot
     * @return bool
     */
    abstract public function match(NodeInterface $token, Element $node, Element $effectiveRoot = null): bool;
}