1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the league/commonmark package.
7 *
8 * (c) Colin O'Dell <colinodell@gmail.com>
9 *
10 * For the full copyright and license information, please view the LICENSE
11 * file that was distributed with this source code.
12 */
13
14namespace League\CommonMark\Node;
15
16use League\CommonMark\Node\Query\AndExpr;
17use League\CommonMark\Node\Query\OrExpr;
18
19final class Query
20{
21    /** @var callable(Node): bool $condition */
22    private $condition;
23
24    public function __construct()
25    {
26        $this->condition = new AndExpr();
27    }
28
29    public function where(callable ...$conditions): self
30    {
31        return $this->andWhere(...$conditions);
32    }
33
34    public function andWhere(callable ...$conditions): self
35    {
36        if ($this->condition instanceof AndExpr) {
37            foreach ($conditions as $condition) {
38                $this->condition->add($condition);
39            }
40        } else {
41            $this->condition = new AndExpr($this->condition, ...$conditions);
42        }
43
44        return $this;
45    }
46
47    public function orWhere(callable ...$conditions): self
48    {
49        if ($this->condition instanceof OrExpr) {
50            foreach ($conditions as $condition) {
51                $this->condition->add($condition);
52            }
53        } else {
54            $this->condition = new OrExpr($this->condition, ...$conditions);
55        }
56
57        return $this;
58    }
59
60    public function findOne(Node $node): ?Node
61    {
62        foreach ($node->iterator() as $n) {
63            if (\call_user_func($this->condition, $n)) {
64                return $n;
65            }
66        }
67
68        return null;
69    }
70
71    /**
72     * @return iterable<Node>
73     */
74    public function findAll(Node $node, ?int $limit = PHP_INT_MAX): iterable
75    {
76        $resultCount = 0;
77
78        foreach ($node->iterator() as $n) {
79            if ($resultCount >= $limit) {
80                break;
81            }
82
83            if (! \call_user_func($this->condition, $n)) {
84                continue;
85            }
86
87            ++$resultCount;
88
89            yield $n;
90        }
91    }
92
93    /**
94     * @return callable(Node): bool
95     */
96    public static function type(string $class): callable
97    {
98        return static fn (Node $node): bool => $node instanceof $class;
99    }
100
101    /**
102     * @psalm-param ?callable(Node): bool $condition
103     *
104     * @return callable(Node): bool
105     */
106    public static function hasChild(?callable $condition = null): callable
107    {
108        return static function (Node $node) use ($condition): bool {
109            foreach ($node->children() as $child) {
110                if ($condition === null || $condition($child)) {
111                    return true;
112                }
113            }
114
115            return false;
116        };
117    }
118
119    /**
120     * @psalm-param ?callable(Node): bool $condition
121     *
122     * @return callable(Node): bool
123     */
124    public static function hasParent(?callable $condition = null): callable
125    {
126        return static function (Node $node) use ($condition): bool {
127            $parent = $node->parent();
128            if ($parent === null) {
129                return false;
130            }
131
132            if ($condition === null) {
133                return true;
134            }
135
136            return $condition($parent);
137        };
138    }
139}
140