diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..2d06cdc528 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +config: + @php bin/console compile:configuration + +watch: + @echo 'config/configuration.yml' | entr make config + +.PHONY: config watch diff --git a/bin/console b/bin/console index 7845c5e2de..3c087c5d48 100755 --- a/bin/console +++ b/bin/console @@ -14,6 +14,7 @@ namespace KonsoleKommander; use Alchemy\Phrasea\Command\Plugin\ListPlugin; use Alchemy\Phrasea\Command\Setup\H264ConfigurationDumper; use Alchemy\Phrasea\Command\Setup\H264MappingGenerator; +use Alchemy\Phrasea\Command\SearchEngine\Debug\QueryParseCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexCreateCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexDropCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexFull; @@ -127,6 +128,7 @@ if ($cli['search_engine.type'] === SearchEngineInterface::TYPE_ELASTICSEARCH) { $cli->command(new IndexCreateCommand()); $cli->command(new IndexDropCommand()); $cli->command(new IndexPopulateCommand()); + $cli->command(new QueryParseCommand()); } $cli->command(new WebsocketServer('ws-server:run')); diff --git a/composer.json b/composer.json index 524858dfd2..6aefcc37bc 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,8 @@ "vierbergenlars/php-semver" : "~2.1", "zend/gdata" : "~1.12.1", "doctrine/migrations" : "1.0.x-dev@dev", - "willdurand/negotiation" : "~1.3" + "willdurand/negotiation" : "~1.3", + "hoa/compiler": "2.14.09.23" }, "require-dev": { "phpunit/phpunit" : "~3.7", @@ -83,7 +84,9 @@ "behat/mink-extension" : "~1.0", "behat/mink-goutte-driver" : "~1.0", "behat/mink-selenium2-driver" : "~1.0", - "fabpot/goutte" : "~1.0" + "fabpot/goutte" : "~1.0", + "hoa/dispatcher": "0.14.09.23", + "hoa/console": "2.14.09.23" }, "autoload": { "psr-0": { diff --git a/composer.lock b/composer.lock index 1bdc24a8a6..74f08b1144 100644 --- a/composer.lock +++ b/composer.lock @@ -4,22 +4,16 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "69bf40de460d2f7a3407f1b905dde913", + "hash": "73cfb65537610dfd0edc9cf6485c4ebb", "packages": [ { "name": "alchemy-fr/tcpdf-clone", "version": "6.0.039", "source": { "type": "git", - "url": "https://github.com/alchemy-fr/tcpdf-clone.git", + "url": "https://github.com/alchemy-fr/tcpdf-clone", "reference": "2ba0248a7187f1626df6c128750650416267f0e7" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/tcpdf-clone/zipball/2ba0248a7187f1626df6c128750650416267f0e7", - "reference": "2ba0248a7187f1626df6c128750650416267f0e7", - "shasum": "" - }, "require": { "php": ">=5.3.0" }, @@ -66,10 +60,6 @@ "qrcode", "tcpdf" ], - "support": { - "source": "https://github.com/alchemy-fr/tcpdf-clone/tree/6.0.039", - "issues": "https://github.com/alchemy-fr/tcpdf-clone/issues" - }, "time": "2013-10-13 16:11:17" }, { @@ -113,7 +103,7 @@ "homepage": "http://www.lickmychip.com/" }, { - "name": "nlegoff", + "name": "Nicolas Le Goff", "email": "legoff.n@gmail.com" }, { @@ -301,7 +291,7 @@ } }, "notification-url": "https://packagist.org/downloads/", - "time": "2014-07-07 16:25:07" + "time": "2014-07-07 14:01:51" }, { "name": "alchemy/task-manager", @@ -372,12 +362,12 @@ "source": { "type": "git", "url": "https://github.com/alchemy-fr/Zippy.git", - "reference": "08008f82957b7dc2b54574b506687b33ecfe0589" + "reference": "d2f5e88f2436b9c1294e8819d951822abe39e9a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/Zippy/zipball/08008f82957b7dc2b54574b506687b33ecfe0589", - "reference": "08008f82957b7dc2b54574b506687b33ecfe0589", + "url": "https://api.github.com/repos/alchemy-fr/Zippy/zipball/d2f5e88f2436b9c1294e8819d951822abe39e9a7", + "reference": "d2f5e88f2436b9c1294e8819d951822abe39e9a7", "shasum": "" }, "require": { @@ -426,7 +416,7 @@ "tar", "zip" ], - "time": "2014-09-03 08:10:41" + "time": "2014-05-05 13:39:00" }, { "name": "cboden/ratchet", @@ -747,7 +737,7 @@ { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -935,8 +925,7 @@ { "name": "Jonathan Wage", "email": "jonwage@gmail.com", - "homepage": "http://www.jwage.com/", - "role": "Creator" + "homepage": "http://www.jwage.com/" }, { "name": "Guilherme Blanco", @@ -954,7 +943,7 @@ { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -1008,7 +997,7 @@ { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -1026,12 +1015,12 @@ "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "1a9dffa64e33fdc10f4b4c3f5d7230b74d4a1021" + "reference": "4256449c5e2603a6b6ee5a78c7c4521d4d4430b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/1a9dffa64e33fdc10f4b4c3f5d7230b74d4a1021", - "reference": "1a9dffa64e33fdc10f4b4c3f5d7230b74d4a1021", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/4256449c5e2603a6b6ee5a78c7c4521d4d4430b8", + "reference": "4256449c5e2603a6b6ee5a78c7c4521d4d4430b8", "shasum": "" }, "require": { @@ -1062,12 +1051,14 @@ ], "authors": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/", + "role": "Creator" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" } ], "description": "Database Schema migrations using Doctrine DBAL", @@ -1076,7 +1067,7 @@ "database", "migrations" ], - "time": "2014-08-18 18:03:07" + "time": "2014-07-09 07:58:02" }, { "name": "doctrine/orm", @@ -1213,13 +1204,13 @@ "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d" + "url": "https://github.com/igorw/evenement", + "reference": "v1.0.0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/fa966683e7df3e5dd5929d984a44abfbd6bafe8d", - "reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d", + "url": "https://github.com/igorw/evenement/zipball/v1.0.0", + "reference": "v1.0.0", "shasum": "" }, "require": { @@ -1246,7 +1237,7 @@ "keywords": [ "event-dispatcher" ], - "time": "2012-05-30 15:01:08" + "time": "2012-05-30 08:01:08" }, { "name": "facebook/php-sdk", @@ -1299,12 +1290,12 @@ "version": "v2.3.9", "source": { "type": "git", - "url": "https://github.com/Atlantic18/DoctrineExtensions.git", + "url": "https://github.com/l3pp4rd/DoctrineExtensions.git", "reference": "35adcaae1a3f50d0d5b73aa50ed8fd28ee35ce54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/35adcaae1a3f50d0d5b73aa50ed8fd28ee35ce54", + "url": "https://api.github.com/repos/l3pp4rd/DoctrineExtensions/zipball/35adcaae1a3f50d0d5b73aa50ed8fd28ee35ce54", "reference": "35adcaae1a3f50d0d5b73aa50ed8fd28ee35ce54", "shasum": "" }, @@ -1372,7 +1363,7 @@ "tree", "uploadable" ], - "time": "2014-01-12 16:34:06" + "time": "2013-08-18 07:18:44" }, { "name": "goodby/csv", @@ -1429,7 +1420,7 @@ "export", "import" ], - "time": "2013-11-22 19:10:34" + "time": "2014-01-12 16:34:06" }, { "name": "guzzle/guzzle", @@ -1523,6 +1514,433 @@ ], "time": "2014-08-11 04:32:36" }, + { + "name": "hoa/compiler", + "version": "2.14.09.23", + "target-dir": "Hoa/Compiler", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Compiler.git", + "reference": "51cdc8b21d13f2fcaa3f3a0d114247534849f8cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Compiler/zipball/51cdc8b21d13f2fcaa3f3a0d114247534849f8cb", + "reference": "51cdc8b21d13f2fcaa3f3a0d114247534849f8cb", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0", + "hoa/file": "~0.0", + "hoa/iterator": "~0.0", + "hoa/math": "~0.0", + "hoa/visitor": "~0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Compiler": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Compiler library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "algebraic", + "ast", + "compiler", + "context-free", + "coverage", + "exhaustive", + "grammar", + "isotropic", + "language", + "lexer", + "library", + "ll1", + "llk", + "parser", + "pp", + "random", + "regular", + "rule", + "sampler", + "syntax", + "token", + "trace", + "uniform" + ], + "time": "2014-09-23 09:50:46" + }, + { + "name": "hoa/core", + "version": "2.14.09.23", + "target-dir": "Hoa/Core", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Core.git", + "reference": "e50354e69e451478223d1d0c1ce4f5d741ea7576" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Core/zipball/e50354e69e451478223d1d0c1ce4f5d741ea7576", + "reference": "e50354e69e451478223d1d0c1ce4f5d741ea7576", + "shasum": "" + }, + "require": { + "ext-spl": "*", + "php": ">=5.4.0" + }, + "suggest": { + "ext-mbstring": "ext/mbstring must be present (or a third implementation).", + "hoa/console": "To use the `hoa` script.", + "hoa/dispatcher": "To use the `hoa` script.", + "hoa/router": "To use the `hoa` script." + }, + "bin": [ + "Bin/hoa" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Core": "." + }, + "files": [ + "Core.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Core library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "consistency", + "core", + "data", + "event", + "library", + "listener", + "parameter", + "protocol" + ], + "time": "2014-09-23 09:45:22" + }, + { + "name": "hoa/file", + "version": "0.14.09.23", + "target-dir": "Hoa/File", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/File.git", + "reference": "a39f62a28256180606115416cf27774966cf73e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/File/zipball/a39f62a28256180606115416cf27774966cf73e9", + "reference": "a39f62a28256180606115416cf27774966cf73e9", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0", + "hoa/iterator": "~0.0", + "hoa/stream": "~0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\File": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\File library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "Socket", + "directory", + "file", + "finder", + "library", + "link", + "temporary" + ], + "time": "2014-09-23 09:50:42" + }, + { + "name": "hoa/iterator", + "version": "0.14.09.23", + "target-dir": "Hoa/Iterator", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Iterator.git", + "reference": "1ca570cab25ca359a1a9f4b4c449d49771fc6a5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/1ca570cab25ca359a1a9f4b4c449d49771fc6a5e", + "reference": "1ca570cab25ca359a1a9f4b4c449d49771fc6a5e", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Iterator": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Iterator library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "iterator", + "library" + ], + "time": "2014-09-23 09:50:40" + }, + { + "name": "hoa/math", + "version": "0.14.09.23", + "target-dir": "Hoa/Math", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Math.git", + "reference": "b52764f602095b4595658f581a504f039cef8d56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Math/zipball/b52764f602095b4595658f581a504f039cef8d56", + "reference": "b52764f602095b4595658f581a504f039cef8d56", + "shasum": "" + }, + "require": { + "hoa/compiler": "~2.0", + "hoa/core": "~2.0", + "hoa/iterator": "~0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Math": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Math library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "arrangement", + "combination", + "combinatorics", + "counting", + "library", + "math", + "permutation", + "sampler", + "set" + ], + "time": "2014-09-23 14:02:37" + }, + { + "name": "hoa/stream", + "version": "0.14.09.23", + "target-dir": "Hoa/Stream", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Stream.git", + "reference": "eaf9bfeb633b8a6bf0fba55e9c035db431024869" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/eaf9bfeb633b8a6bf0fba55e9c035db431024869", + "reference": "eaf9bfeb633b8a6bf0fba55e9c035db431024869", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Stream": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Stream library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "Context", + "bucket", + "composite", + "filter", + "in", + "library", + "out", + "protocol", + "stream", + "wrapper" + ], + "time": "2014-09-23 09:50:38" + }, + { + "name": "hoa/visitor", + "version": "0.14.09.23", + "target-dir": "Hoa/Visitor", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Visitor.git", + "reference": "071523b6677466979e57a9f9ef61a46b76221935" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Visitor/zipball/071523b6677466979e57a9f9ef61a46b76221935", + "reference": "071523b6677466979e57a9f9ef61a46b76221935", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Visitor": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Visitor library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "library", + "structure", + "visit", + "visitor" + ], + "time": "2014-09-23 09:50:51" + }, { "name": "igorw/get-in", "version": "v1.0.2", @@ -1596,7 +2014,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.7-dev" + "dev-develop": "0.6-dev" } }, "autoload": { @@ -1623,7 +2041,7 @@ "image processing" ], "support": { - "source": "https://github.com/nlegoff/Imagine/tree/flatten-layer" + "source": "https://github.com/alchemy-fr/Imagine/tree/0.6.1-flatten-layer" }, "time": "2014-10-08 16:23:33" }, @@ -1932,9 +2350,9 @@ ], "authors": [ { - "name": "Johannes Schmitt", + "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -2249,7 +2667,7 @@ ], "authors": [ { - "name": "Steve Clay", + "name": "Stephen Clay", "email": "steve@mrclay.org", "homepage": "http://www.mrclay.org/", "role": "Developer" @@ -2435,21 +2853,21 @@ "source": { "type": "git", "url": "https://github.com/romainneutron/Imagine-Silex-Service-Provider.git", - "reference": "a8a7862ae90419f2b23746cd8436c2310e4eb084" + "reference": "0.1.2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/romainneutron/Imagine-Silex-Service-Provider/zipball/a8a7862ae90419f2b23746cd8436c2310e4eb084", - "reference": "a8a7862ae90419f2b23746cd8436c2310e4eb084", + "url": "https://api.github.com/repos/romainneutron/Imagine-Silex-Service-Provider/zipball/0.1.2", + "reference": "0.1.2", "shasum": "" }, "require": { "imagine/imagine": "*", "php": ">=5.3.3", - "silex/silex": "~1.0" + "silex/silex": ">=1.0,<2.0" }, "require-dev": { - "symfony/browser-kit": "~2.0" + "symfony/browser-kit": ">=2.0,<3.0" }, "type": "library", "autoload": { @@ -2963,7 +3381,7 @@ "metadata" ], "support": { - "source": "https://github.com/alchemy-fr/PHPExiftool/tree/dev" + "source": "https://github.com/alchemy-fr/PHPExiftool/tree/0.4.1-mwg-metadata-copy" }, "time": "2014-10-08 16:09:02" }, @@ -3001,9 +3419,9 @@ ], "authors": [ { - "name": "Johannes Schmitt", + "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -3560,7 +3978,7 @@ }, { "name": "Phraseanet Team", - "email": "info@alchemy.fr", + "email": "support@alchemy.fr", "homepage": "http://www.phraseanet.com/" } ], @@ -3607,9 +4025,7 @@ "authors": [ { "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" + "email": "fabien@symfony.com" }, { "name": "Chris Corbyn" @@ -4522,6 +4938,242 @@ ], "time": "2014-10-09 15:52:51" }, + { + "name": "hoa/console", + "version": "2.14.09.23", + "target-dir": "Hoa/Console", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Console.git", + "reference": "8466f74ddb5bd323357fe5464629c948a1fd8d25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Console/zipball/8466f74ddb5bd323357fe5464629c948a1fd8d25", + "reference": "8466f74ddb5bd323357fe5464629c948a1fd8d25", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0", + "hoa/stream": "~0.0", + "hoa/string": "~2.0" + }, + "suggest": { + "ext-pcntl": "To enable hoa://Event/Console/Window:resize.", + "hoa/dispatcher": "To use the console kit.", + "hoa/router": "To use the console kit." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Console": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Console library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "autocompletion", + "chrome", + "cli", + "console", + "cursor", + "getoption", + "library", + "option", + "parser", + "processus", + "readline", + "terminfo", + "tput", + "window" + ], + "time": "2014-09-23 14:17:08" + }, + { + "name": "hoa/dispatcher", + "version": "0.14.09.23", + "target-dir": "Hoa/Dispatcher", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Dispatcher.git", + "reference": "82924823fa9cedad9775f1ab4d51075e980ac2c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Dispatcher/zipball/82924823fa9cedad9775f1ab4d51075e980ac2c6", + "reference": "82924823fa9cedad9775f1ab4d51075e980ac2c6", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0" + }, + "suggest": { + "hoa/router": "Provide routers.", + "hoa/view": "Provide view interface." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Dispatcher": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Dispatcher library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "dispatcher", + "kit", + "library" + ], + "time": "2014-09-23 14:12:43" + }, + { + "name": "hoa/router", + "version": "2.14.09.23", + "target-dir": "Hoa/Router", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Router.git", + "reference": "8937785aecf7ca3b6dbd5f668eb97e15c672758d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Router/zipball/8937785aecf7ca3b6dbd5f668eb97e15c672758d", + "reference": "8937785aecf7ca3b6dbd5f668eb97e15c672758d", + "shasum": "" + }, + "require": { + "hoa/core": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\Router": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\Router library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "cli", + "http", + "library", + "router" + ], + "time": "2014-09-23 09:50:57" + }, + { + "name": "hoa/string", + "version": "2.14.09.23", + "target-dir": "Hoa/String", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/String.git", + "reference": "c385ffb1382d919c63ab97acd6f62058179c5f2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/String/zipball/c385ffb1382d919c63ab97acd6f62058179c5f2a", + "reference": "c385ffb1382d919c63ab97acd6f62058179c5f2a", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "hoa/core": "~2.0" + }, + "suggest": { + "ext-intl": "To get a better Hoa\\String::toAscii() and Hoa\\String::compareTo()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Hoa\\String": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "http://hoa-project.net/" + } + ], + "description": "The Hoa\\String library.", + "homepage": "http://hoa-project.net/", + "keywords": [ + "library", + "search", + "string", + "unicode" + ], + "time": "2014-09-23 09:55:55" + }, { "name": "instaclick/php-webdriver", "version": "1.4.0", diff --git a/grammar/arithmetic.pp b/grammar/arithmetic.pp new file mode 100644 index 0000000000..b1e110874c --- /dev/null +++ b/grammar/arithmetic.pp @@ -0,0 +1,90 @@ +// +// Hoa +// +// +// @license +// +// New BSD License +// +// Copyright © 2007-2014, Ivan Enderlin. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Hoa nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Grammar \Hoa\Math\Arithmetic. +// +// Provide a grammar for arithmetic expressions. +// +// @author Stéphane Py +// @author Sébastien Houze +// @author Ivan Enderlin +// @copyright Copyright © 2007-2014 Stéphane Py, Sébastien Houze, Ivan Enderlin. +// @license New BSD License +// + + +%skip space \s +%token bracket_ \( +%token _bracket \) +%token comma , +%token number (0|[1-9]\d*)(\.\d+)?([eE][\+\-]?\d+)? +%token plus \+ +%token minus \-|− +%token times \*|× +%token div /|÷ +%token constant [A-Z_]+[A-Z0-9_]+ +%token id \w+ + +expression: + primary() ( ::plus:: #addition expression() )? + +primary: + secondary() ( ::minus:: #substraction expression() )? + +secondary: + ternary() ( ::times:: #multiplication expression() )? + +ternary: + term() ( ::div:: #division expression() )? + +term: + ( ::bracket_:: expression() ::_bracket:: #group ) + | number() + | constant() + | variable() + | ( ::minus:: #negative | ::plus:: ) term() + | function() + +number: + + +constant: + + +#variable: + + +#function: + ::bracket_:: + ( expression() ( ::comma:: expression() )* )? + ::_bracket:: diff --git a/grammar/json.pp b/grammar/json.pp new file mode 100644 index 0000000000..5a21ef5288 --- /dev/null +++ b/grammar/json.pp @@ -0,0 +1,36 @@ +%skip space \s + +%token true true +%token false false +%token null null +%token quote_ " -> string +%token string:escaped \\(["\\/bfnrt]|u[0-9a-fA-F]{4}) +%token string:string [^"\\]+ +%token string:_quote " -> default +%token brace_ { +%token _brace } +%token bracket_ \[ +%token _bracket \] +%token colon : +%token comma , +%token number \-?(0|[1-9]\d*)(\.\d+)?([eE][\+\-]?\d+)? + +value: + | | | string() | object() | array() | number() + +string: + ::quote_:: + + ::_quote:: + +number: + + +#object: + ::brace_:: pair() ( ::comma:: pair() )* ::_brace:: + +#pair: + string() ::colon:: value() + +#array: + ::bracket_:: value() ( ::comma:: value() )* ::_bracket:: \ No newline at end of file diff --git a/grammar/query.pp b/grammar/query.pp new file mode 100644 index 0000000000..39a54971c1 --- /dev/null +++ b/grammar/query.pp @@ -0,0 +1,47 @@ +%skip space \s + +%token bracket_ \( +%token _bracket \) +%token quote_ " -> string +%token string:string [^"]+ +%token string:_quote " -> default +%token in IN +%token and AND +%token or OR +%token except EXCEPT +%token word [^\s\(\)]+ + +// relative order of precedence is NOT > XOR > AND > OR + +#query: + primary() + +primary: + secondary() ( ::except:: #except primary() )? + +secondary: + ternary() ( ::or:: #or primary() )? + +ternary: + quaternary() ( ::and:: #and primary() )? + +quaternary: + term() ( ::in:: #in word() )? + +term: + ( ::bracket_:: primary() ::_bracket:: ) | text() + +#text: + ( word() | keyword() | symbol() )+ + +word: + | string() + +string: + ::quote_:: ::_quote:: + +keyword: + | | | + +symbol: + ::bracket_:: | ::_bracket:: diff --git a/lib/Alchemy/Phrasea/Command/SearchEngine/Debug/QueryParseCommand.php b/lib/Alchemy/Phrasea/Command/SearchEngine/Debug/QueryParseCommand.php new file mode 100644 index 0000000000..4ab3bad78b --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/SearchEngine/Debug/QueryParseCommand.php @@ -0,0 +1,80 @@ +setName('searchengine:query:parse') + ->setDescription('Debug a search query') + ->addArgument( + 'query', + InputArgument::REQUIRED, + 'The query to debug' + ) + ->addOption( + 'compiler-dump', + 'd', + InputOption::VALUE_NONE, + 'Output underlying compiler AST before visitor processing' + ) + ->addOption( + 'no-compiler-postprocessing', + 'p', + InputOption::VALUE_NONE, + 'Prevent AST optimization before visitor processing' + ) + ->addOption( + 'raw', + null, + InputOption::VALUE_NONE, + 'Only output query dump' + ) + ; + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $string = $input->getArgument('query'); + $raw = $input->getOption('raw'); + + if (!$raw) { + $output->writeln(sprintf('Parsing search query: %s', $string)); + $output->writeln(str_repeat('-', 20)); + } + + $postprocessing = !$input->getOption('no-compiler-postprocessing'); + + $parser = $this->container['query_parser']; + if ($input->getOption('compiler-dump')) { + $dump = $parser->dump($string, $postprocessing); + } else { + $query = $parser->parse($string, $postprocessing); + $dump = $query->dump(); + } + + if (!$raw) { + $output->writeln($dump); + } else { + $output->write($dump); + } + } +} diff --git a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php index e848d29a01..860337ddf6 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php @@ -18,10 +18,13 @@ use Alchemy\Phrasea\SearchEngine\Elastic\ElasticSearchEngine; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryParser; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus; use Alchemy\Phrasea\SearchEngine\Phrasea\PhraseaEngine; use Alchemy\Phrasea\SearchEngine\Phrasea\PhraseaEngineSubscriber; use Elasticsearch\Client; +use Hoa\Compiler\Llk\Llk; +use Hoa\File\Read; use Monolog\Handler\RotatingFileHandler; use Monolog\Logger; use Silex\Application; @@ -126,6 +129,21 @@ class SearchEngineServiceProvider implements ServiceProviderInterface $app['elasticsearch.options']['index'] ); }); + + $app['query_parser.grammar_path'] = function ($app) { + $configPath = ['registry', 'searchengine', 'query-grammar-path']; + $grammarPath = $app['conf']->get($configPath, 'grammar/query.pp'); + $projectRoot = '../../../../..'; + + return realpath(implode('/', [__DIR__, $projectRoot, $grammarPath])); + }; + + $app['query_parser'] = $app->share(function ($app) { + $grammarPath = $app['query_parser.grammar_path']; + $parser = Llk::load(new Read($grammarPath)); + + return new QueryParser($parser); + }); } public function boot(Application $app) diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AndExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AndExpression.php index 576851d38a..99e5579c64 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AndExpression.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AndExpression.php @@ -2,48 +2,19 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST; -class AndExpression extends Node +class AndExpression extends BinaryOperator { - protected $members = array(); - - public function __construct(Node $left, Node $right) - { - $this->members[] = $left; - $this->members[] = $right; - } - - public function getMembers() - { - return $this->members; - } + protected $operator = 'AND'; public function getQuery($fields = ['_all']) { - $rules = array(); - foreach ($this->members as $member) { - $rules[] = $member->getQuery($fields); - } + $left = $this->left->getQuery($fields); + $right = $this->right->getQuery($fields); return array( 'bool' => array( - 'must' => count($rules) > 1 ? $rules : $rules[0] + 'must' => array($left, $right) ) ); } - - public function __toString() - { - return sprintf('(%s)', implode(' AND ', $this->members)); - } - - public function isFullTextOnly() - { - foreach ($this->members as $member) { - if (!$member->isFullTextOnly()) { - return false; - } - } - - return true; - } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/BinaryOperator.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/BinaryOperator.php new file mode 100644 index 0000000000..54c41a4514 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/BinaryOperator.php @@ -0,0 +1,27 @@ +left = $left; + $this->right = $right; + } + + public function __toString() + { + return sprintf('(%s %s %s)', $this->left, $this->operator, $this->right); + } + + public function isFullTextOnly() + { + return $this->left->isFullTextOnly() + && $this->right->isFullTextOnly(); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/ExceptExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/ExceptExpression.php new file mode 100644 index 0000000000..a1c812b444 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/ExceptExpression.php @@ -0,0 +1,21 @@ +left->getQuery($fields); + $right = $this->right->getQuery($fields); + + return array( + 'bool' => array( + 'must' => $left, + 'must_not' => $right + ) + ); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeywordNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldNode.php similarity index 94% rename from lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeywordNode.php rename to lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldNode.php index 0bdaca7d65..f15de1b18e 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeywordNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldNode.php @@ -2,7 +2,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST; -class KeywordNode extends Node +class FieldNode extends Node { protected $keyword; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/InExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/InExpression.php index 5c9c7283a4..fb8f8fad54 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/InExpression.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/InExpression.php @@ -7,9 +7,9 @@ class InExpression extends Node protected $field; protected $expression; - public function __construct(KeywordNode $keyword, $expression) + public function __construct(FieldNode $field, $expression) { - $this->field = $keyword; + $this->field = $field; $this->expression = $expression; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/OrExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/OrExpression.php index bbf9ab65ef..6a345688e5 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/OrExpression.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/OrExpression.php @@ -2,48 +2,19 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST; -class OrExpression extends Node +class OrExpression extends BinaryOperator { - protected $members = array(); - - public function __construct(Node $left, Node $right) - { - $this->members[] = $left; - $this->members[] = $right; - } - - public function getMembers() - { - return $this->members; - } + protected $operator = 'OR'; public function getQuery($fields = ['_all']) { - $rules = array(); - foreach ($this->members as $member) { - $rules[] = $member->getQuery($fields); - } + $left = $this->left->getQuery($fields); + $right = $this->right->getQuery($fields); return array( 'bool' => array( - 'should' => count($rules) > 1 ? $rules : $rules[0] + 'should' => array($left, $right) ) ); } - - public function __toString() - { - return sprintf('(%s)', implode(' OR ', $this->members)); - } - - public function isFullTextOnly() - { - foreach ($this->members as $member) { - if (!$member->isFullTextOnly()) { - return false; - } - } - - return true; - } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/QuotedTextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/QuotedTextNode.php index ba06da1212..35df144d6c 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/QuotedTextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/QuotedTextNode.php @@ -8,9 +8,10 @@ class QuotedTextNode extends TextNode { return array( 'multi_match' => array( + 'type' => 'phrase', 'fields' => $fields, 'query' => $this->text, - 'operator' => 'and' + // 'operator' => 'and' ) ); } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php index cc31ee038d..c96aad7d4f 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php @@ -30,4 +30,9 @@ class TextNode extends Node { return true; } + + public function getText() + { + return $this->text; + } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index b4ea5804cd..1012a28f76 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\SearchQuery; use Alchemy\Phrasea\SearchEngine\SearchEngineInterface; use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; use Alchemy\Phrasea\SearchEngine\SearchEngineResult; @@ -270,8 +271,8 @@ class ElasticSearchEngine implements SearchEngineInterface public function query($string, $offset, $perPage, SearchEngineOptions $options = null) { $options = $options ?: new SearchEngineOptions(); - $parser = new QueryParser(); - $ast = $parser->parse($string); + + $searchQuery = $this->app['query_parser']->parse($string); // Contains the full thesaurus paths to search on $pathsToFilter = []; @@ -279,9 +280,9 @@ class ElasticSearchEngine implements SearchEngineInterface $collectFields = []; // Only search in thesaurus for full text search - if ($ast->isFullTextOnly()) { + if ($searchQuery->isFullTextOnly()) { $termFields = $this->expendToAnalyzedFieldsNames('value', null, $this->app['locale']); - $termsQuery = $ast->getQuery($termFields); + $termsQuery = $searchQuery->getElasticsearchQuery($termFields); $params = $this->createTermQueryParams($termsQuery, $options); $terms = $this->doExecute('search', $params); @@ -313,7 +314,7 @@ class ElasticSearchEngine implements SearchEngineInterface $recordQuery = [ 'bool' => [ 'should' => [ - $ast->getQuery($recordFields) + $searchQuery->getElasticsearchQuery($recordFields) ] ] ]; @@ -376,7 +377,7 @@ class ElasticSearchEngine implements SearchEngineInterface $results[] = new \record_adapter($this->app, $databoxId, $recordId, $n++); } - $query['_ast'] = (string) $ast; + $query['_ast'] = $searchQuery->dump(); $query['_paths'] = $pathsToFilter; $query['_richFields'] = $collectFields; $query['query'] = json_encode($params); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Exception/QueryException.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Exception/QueryException.php new file mode 100644 index 0000000000..b38ea30a72 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Exception/QueryException.php @@ -0,0 +1,16 @@ +root = $root; + } + + /* + * This method seems weird to me, the implementation returns true when the + * query doesn't contain IN statements, but that doesn't define a full text + * search. + */ + public function isFullTextOnly() + { + return $this->root->isFullTextOnly(); + } + + public function getElasticsearchQuery($fields = array()) + { + return $this->root->getQuery($fields); + } + + public function dump() + { + return $this->root->__toString(); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryParser.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryParser.php new file mode 100644 index 0000000000..f4be7af627 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryParser.php @@ -0,0 +1,108 @@ +parser = $parser; + } + + /** + * Creates a Query object from a string + */ + public function parse($string, $postprocessing = true) + { + return $this->visitString($string, new QueryVisitor(), $postprocessing); + } + + public function dump($string, $postprocessing = true) + { + return $this->visitString($string, new DumpVisitor(), $postprocessing); + } + + private function visitString($string, Visit $visitor, $postprocessing = true) + { + try { + $ast = $this->parser->parse($string); + } catch (CompilerException $e) { + throw new QueryException('Provided query is not valid', 0, $e); + } + + if ($postprocessing) { + $this->fixOperatorAssociativity($ast); + } + + return $visitor->visit($ast); + } + + /** + * Walks the tree to restore left-associativity of some operators + * + * @param TreeNode $root AST root node + */ + private function fixOperatorAssociativity(TreeNode &$root) + { + switch ($root->getChildrenNumber()) { + case 0: + // Leaf nodes can't be rotated, and have no childs + return; + + case 2: + // We only want to rotate tree contained in the left associative + // subset of operators + $rootType = $root->getId(); + if (!in_array($rootType, self::$leftAssociativeOperators)) { + break; + } + // Do not break operator precedence + $pivot = $root->getChild(1); + if ($pivot->getId() !== $rootType) { + break; + } + $this->leftRotateTree($root); + break; + } + + // Recursively fix tree branches + $children = $root->getChildren(); + foreach ($children as $index => $_) { + $this->fixOperatorAssociativity($children[$index]); + } + $root->setChildren($children); + } + + private function leftRotateTree(TreeNode &$root) + { + // Pivot = Root.Left + $pivot = $root->getChild(1); + // Root.Right = Pivot.Left + $children = $root->getChildren(); + $children[1] = $pivot->getChild(0); // Pivot, rotation side + $root->setChildren($children); + // Pivot.Left = Root + $children = $pivot->getChildren(); + $children[0] = $root; + $pivot->setChildren($children); + // Root = Pivot + $root = $pivot; + } +} + diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php new file mode 100644 index 0000000000..a60dba1b85 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -0,0 +1,132 @@ +getValue()) { + return $this->visitToken($value['token'], $value['value']); + } + + return $this->visitNode($element); + } + + private function visitToken($token, $value) + { + switch ($token) { + case NodeTypes::TOKEN_WORD: + return new AST\TextNode($value); + + case NodeTypes::TOKEN_STRING: + return new AST\QuotedTextNode($value); + + default: + // Generic handling off other tokens for unresctricted text + return new AST\TextNode($value); + } + } + + private function visitNode(Element $element) + { + switch ($element->getId()) { + case NodeTypes::QUERY: + return $this->visitQuery($element); + + case NodeTypes::IN_EXPR: + return $this->visitInNode($element); + + case NodeTypes::AND_EXPR: + return $this->visitAndNode($element); + + case NodeTypes::OR_EXPR: + return $this->visitOrNode($element); + + case NodeTypes::EXCEPT_EXPR: + return $this->visitExceptNode($element); + + case NodeTypes::TEXT: + return $this->visitText($element); + + default: + throw new \Exception(sprintf('Unknown node type "%s".', $element->getId())); + } + } + + private function visitQuery(Element $element) + { + foreach ($element->getChildren() as $child) { + $root = $child->accept($this); + } + return new Query($root); + } + + private function visitInNode(Element $element) + { + if ($element->getChildrenNumber() !== 2) { + throw new \Exception('IN expression can only have 2 childs.'); + } + $expression = $element->getChild(0)->accept($this); + $field = new AST\FieldNode($element->getChild(1)->getValue()['value']); + return new AST\InExpression($field, $expression); + } + + private function visitAndNode(Element $element) + { + return $this->handleBinaryOperator($element, function($left, $right) { + return new AST\AndExpression($left, $right); + }); + } + + private function visitOrNode(Element $element) + { + return $this->handleBinaryOperator($element, function($left, $right) { + return new AST\OrExpression($left, $right); + }); + } + + private function visitExceptNode(Element $element) + { + return $this->handleBinaryOperator($element, function($left, $right) { + return new AST\ExceptExpression($left, $right); + }); + } + + private function handleBinaryOperator(Element $element, \Closure $factory) + { + if ($element->getChildrenNumber() !== 2) { + throw new \Exception('Binary expression can only have 2 childs.'); + } + $left = $element->getChild(0)->accept($this); + $right = $element->getChild(1)->accept($this); + + return $factory($left, $right); + } + + private function visitText(Element $element) + { + $root = null; + foreach ($element->getChildren() as $child) { + $node = $child->accept($this); + if ($root) { + // Merge text nodes together, but not with quoted ones + if ($root instanceof AST\TextNode && + !$root instanceof AST\QuotedTextNode && + !$node instanceof AST\QuotedTextNode) { + $root = new AST\TextNode(sprintf('%s %s', $root->getText(), $node->getText())); + } else { + $root = new AST\AndExpression($root, $node); + } + } else { + $root = $node; + } + } + + return $root; + } +}