From af7a4b5d3da8b20430b88faf83bce56602721c16 Mon Sep 17 00:00:00 2001 From: Sobuno Date: Thu, 2 Jan 2025 23:19:21 +0100 Subject: [PATCH] Renaming of classes, making true recursive structure --- app/Support/Search/OperatorQuerySearch.php | 60 +++++++++++-------- .../QueryParser/{Field.php => FieldNode.php} | 2 +- .../Search/QueryParser/GdbotsQueryParser.php | 15 ++--- .../{Subquery.php => NodeGroup.php} | 6 +- .../Search/QueryParser/QueryParser.php | 27 +++++---- .../QueryParser/QueryParserInterface.php | 4 +- .../QueryParser/{Word.php => StringNode.php} | 4 +- 7 files changed, 66 insertions(+), 52 deletions(-) rename app/Support/Search/QueryParser/{Field.php => FieldNode.php} (96%) rename app/Support/Search/QueryParser/{Subquery.php => NodeGroup.php} (82%) rename app/Support/Search/QueryParser/{Word.php => StringNode.php} (76%) diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 4e9408a3a8..74e518fd5c 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -41,9 +41,10 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\Search\QueryParser\QueryParserInterface; use FireflyIII\Support\Search\QueryParser\Node; -use FireflyIII\Support\Search\QueryParser\Field; -use FireflyIII\Support\Search\QueryParser\Word; -use FireflyIII\Support\Search\QueryParser\Subquery; +use FireflyIII\Support\Search\QueryParser\FieldNode; +use FireflyIII\Support\Search\QueryParser\StringNode; +use FireflyIII\Support\Search\QueryParser\NodeGroup; + use FireflyIII\Support\ParseDateString; use FireflyIII\User; use Illuminate\Pagination\LengthAwarePaginator; @@ -140,7 +141,7 @@ class OperatorQuerySearch implements SearchInterface app('log')->debug(sprintf('Using %s as implementation for QueryParserInterface', get_class($parser))); try { - $nodes = $parser->parse($query); + $parsedQuery = $parser->parse($query); } catch (\LogicException|\TypeError $e) { app('log')->error($e->getMessage()); app('log')->error(sprintf('Could not parse search: "%s".', $query)); @@ -148,10 +149,8 @@ class OperatorQuerySearch implements SearchInterface throw new FireflyException(sprintf('Invalid search value "%s". See the logs.', e($query)), 0, $e); } - app('log')->debug(sprintf('Found %d node(s)', count($nodes))); - foreach ($nodes as $node) { - $this->handleSearchNode($node); - } + app('log')->debug(sprintf('Found %d node(s) at top-level', count($parsedQuery->getNodes()))); + $this->handleSearchNode($parsedQuery); // add missing information $this->collector->withBillInformation(); @@ -170,27 +169,16 @@ class OperatorQuerySearch implements SearchInterface app('log')->debug(sprintf('Now in handleSearchNode(%s)', get_class($node))); switch (true) { - case $node instanceof Word: - $word = (string) $node->getValue(); - if($node->isProhibited()) { - app('log')->debug(sprintf('Exclude word "%s" from search string', $word)); - $this->prohibitedWords[] = $word; - } else { - app('log')->debug(sprintf('Add word "%s" to search string', $word)); - $this->words[] = $word; - } - + case $node instanceof StringNode: + $this->handleStringNode($node); break; - case $node instanceof Field: + case $node instanceof FieldNode: $this->handleFieldNode($node); break; - case $node instanceof Subquery: - //TODO: Handle Subquery prohibition, i.e. flip all prohibition flags inside the subquery - foreach ($node->getNodes() as $subNode) { - $this->handleSearchNode($subNode); - } + case $node instanceof NodeGroup: + $this->handleNodeGroup($node); break; default: @@ -199,10 +187,32 @@ class OperatorQuerySearch implements SearchInterface } } + private function handleNodeGroup(NodeGroup $node): void + { + //TODO: Handle Subquery prohibition, i.e. flip all prohibition flags inside the subquery + foreach ($node->getNodes() as $subNode) { + $this->handleSearchNode($subNode); + } + } + + + + private function handleStringNode(StringNode $node): void + { + $string = (string) $node->getValue(); + if($node->isProhibited()) { + app('log')->debug(sprintf('Exclude string "%s" from search string', $string)); + $this->prohibitedWords[] = $string; + } else { + app('log')->debug(sprintf('Add string "%s" to search string', $string)); + $this->words[] = $string; + } + } + /** * @throws FireflyException */ - private function handleFieldNode(Field $node): void + private function handleFieldNode(FieldNode $node): void { $operator = strtolower($node->getOperator()); $value = $node->getValue(); diff --git a/app/Support/Search/QueryParser/Field.php b/app/Support/Search/QueryParser/FieldNode.php similarity index 96% rename from app/Support/Search/QueryParser/Field.php rename to app/Support/Search/QueryParser/FieldNode.php index 3028af9730..4fd847cd5d 100644 --- a/app/Support/Search/QueryParser/Field.php +++ b/app/Support/Search/QueryParser/FieldNode.php @@ -7,7 +7,7 @@ namespace FireflyIII\Support\Search\QueryParser; /** * Represents a field operator with value (e.g. amount:100) */ -class Field extends Node +class FieldNode extends Node { private string $operator; private string $value; diff --git a/app/Support/Search/QueryParser/GdbotsQueryParser.php b/app/Support/Search/QueryParser/GdbotsQueryParser.php index 8a56d0f6de..c580b694be 100644 --- a/app/Support/Search/QueryParser/GdbotsQueryParser.php +++ b/app/Support/Search/QueryParser/GdbotsQueryParser.php @@ -19,17 +19,18 @@ class GdbotsQueryParser implements QueryParserInterface } /** - * @return Node[] + * @return NodeGroup * @throws FireflyException */ - public function parse(string $query): array + public function parse(string $query): NodeGroup { try { $result = $this->parser->parse($query); - return array_map( + $nodes = array_map( fn(GdbotsNode\Node $node) => $this->convertNode($node), $result->getNodes() ); + return new NodeGroup($nodes); } catch (\LogicException|\TypeError $e) { fwrite(STDERR, "Setting up GdbotsQueryParserTest\n"); dd('Creating GdbotsQueryParser'); @@ -44,17 +45,17 @@ class GdbotsQueryParser implements QueryParserInterface { switch (true) { case $node instanceof GdbotsNode\Word: - return new Word($node->getValue()); + return new StringNode($node->getValue()); case $node instanceof GdbotsNode\Field: - return new Field( + return new FieldNode( $node->getValue(), (string) $node->getNode()->getValue(), BoolOperator::PROHIBITED === $node->getBoolOperator() ); case $node instanceof GdbotsNode\Subquery: - return new Subquery( + return new NodeGroup( array_map( fn(GdbotsNode\Node $subNode) => $this->convertNode($subNode), $node->getNodes() @@ -69,7 +70,7 @@ class GdbotsQueryParser implements QueryParserInterface case $node instanceof GdbotsNode\Mention: case $node instanceof GdbotsNode\Emoticon: case $node instanceof GdbotsNode\Emoji: - return new Word((string) $node->getValue()); + return new StringNode((string) $node->getValue()); default: throw new FireflyException( diff --git a/app/Support/Search/QueryParser/Subquery.php b/app/Support/Search/QueryParser/NodeGroup.php similarity index 82% rename from app/Support/Search/QueryParser/Subquery.php rename to app/Support/Search/QueryParser/NodeGroup.php index f43c1f123a..967c4d89d4 100644 --- a/app/Support/Search/QueryParser/Subquery.php +++ b/app/Support/Search/QueryParser/NodeGroup.php @@ -5,9 +5,11 @@ declare(strict_types=1); namespace FireflyIII\Support\Search\QueryParser; /** - * Represents a subquery (group of nodes) + * Represents a group of nodes. + * + * NodeGroups can be nested inside other NodeGroups, making them subqueries */ -class Subquery extends Node +class NodeGroup extends Node { /** @var Node[] */ private array $nodes; diff --git a/app/Support/Search/QueryParser/QueryParser.php b/app/Support/Search/QueryParser/QueryParser.php index 1d7f0658c9..f1e951f5ce 100644 --- a/app/Support/Search/QueryParser/QueryParser.php +++ b/app/Support/Search/QueryParser/QueryParser.php @@ -30,16 +30,16 @@ class QueryParser implements QueryParserInterface private string $query; private int $position = 0; - /** @return Node[] */ - public function parse(string $query): array + /** @return NodeGroup */ + public function parse(string $query): NodeGroup { $this->query = $query; $this->position = 0; - return $this->parseQuery(false); + return $this->buildNodeGroup(false); } - /** @return Node[] */ - private function parseQuery(bool $isSubquery): array + /** @return NodeGroup */ + private function buildNodeGroup(bool $isSubquery, bool $prohibited = false): NodeGroup { $nodes = []; $nodeResult = $this->buildNextNode($isSubquery); @@ -52,7 +52,7 @@ class QueryParser implements QueryParserInterface $nodeResult = $this->buildNextNode($isSubquery); } - return $nodes; + return new NodeGroup($nodes, $prohibited); } private function buildNextNode(bool $isSubquery): NodeResult @@ -105,8 +105,7 @@ class QueryParser implements QueryParserInterface if ($tokenUnderConstruction === '') { // A left parentheses at the beginning of a token indicates the start of a subquery $this->position++; - return new NodeResult( - new Subquery($this->parseQuery(true), $prohibited), + return new NodeResult($this->buildNodeGroup(true, $prohibited), false ); } else { @@ -161,16 +160,18 @@ class QueryParser implements QueryParserInterface $this->position++; } - return new NodeResult($tokenUnderConstruction !== '' || $fieldName !== '' - ? $this->createNode($tokenUnderConstruction, $fieldName, $prohibited) - : null, true); + $finalNode = $tokenUnderConstruction !== '' || $fieldName !== '' + ? $this->createNode($tokenUnderConstruction, $fieldName, $prohibited) + : null; + + return new NodeResult($finalNode, true); } private function createNode(string $token, string $fieldName, bool $prohibited): Node { if (strlen($fieldName) > 0) { - return new Field(trim($fieldName), trim($token), $prohibited); + return new FieldNode(trim($fieldName), trim($token), $prohibited); } - return new Word(trim($token), $prohibited); + return new StringNode(trim($token), $prohibited); } } diff --git a/app/Support/Search/QueryParser/QueryParserInterface.php b/app/Support/Search/QueryParser/QueryParserInterface.php index 831baf1270..00383af0ff 100644 --- a/app/Support/Search/QueryParser/QueryParserInterface.php +++ b/app/Support/Search/QueryParser/QueryParserInterface.php @@ -7,7 +7,7 @@ namespace FireflyIII\Support\Search\QueryParser; interface QueryParserInterface { /** - * @return Node[] + * @return NodeGroup */ - public function parse(string $query): array; + public function parse(string $query): NodeGroup; } diff --git a/app/Support/Search/QueryParser/Word.php b/app/Support/Search/QueryParser/StringNode.php similarity index 76% rename from app/Support/Search/QueryParser/Word.php rename to app/Support/Search/QueryParser/StringNode.php index d7c12b6976..387862da30 100644 --- a/app/Support/Search/QueryParser/Word.php +++ b/app/Support/Search/QueryParser/StringNode.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace FireflyIII\Support\Search\QueryParser; /** - * Represents a word in the search query, meaning either a single-word without spaces or a quote-delimited string + * Represents a string in the search query, meaning either a single-word without spaces or a quote-delimited string */ -class Word extends Node +class StringNode extends Node { private string $value;