diff --git a/composer.json b/composer.json index be273af..569b05c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "mustangostang/spyc": "^0.5.1", "intervention/image": "^2.4", "guzzlehttp/guzzle": "^6.3", - "lambdacasserole/condense": "^1.1" + "lambdacasserole/condense": "^1.1", + "tburry/pquery": "^1.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index ad0589d..b7634c5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "414181a9b439757b16b4938d109a80bd", + "content-hash": "18fab38459b21165a1075c73d836e92c", "packages": [ { "name": "defuse/php-encryption", @@ -1056,6 +1056,58 @@ ], "time": "2016-06-29T05:41:56+00:00" }, + { + "name": "tburry/pquery", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/tburry/pquery.git", + "reference": "872339ffd38d261c4417ea1855428b1b4ff9abf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tburry/pquery/zipball/872339ffd38d261c4417ea1855428b1b4ff9abf1", + "reference": "872339ffd38d261c4417ea1855428b1b4ff9abf1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "htmlawed/htmlawed": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "IQuery.php", + "gan_formatter.php", + "gan_node_html.php", + "gan_parser_html.php", + "gan_selector_html.php", + "gan_tokenizer.php", + "gan_xml2array.php", + "pQuery.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Todd Burry", + "email": "todd@vanillaforums.com", + "role": "developer" + } + ], + "description": "A jQuery like html dom parser written in php.", + "keywords": [ + "dom", + "ganon", + "php" + ], + "time": "2016-01-14T20:55:00+00:00" + }, { "name": "twig/twig", "version": "v1.24.1", diff --git a/src/CactusSmilesConverter.php b/src/CactusSmilesConverter.php index ccd0688..695c612 100644 --- a/src/CactusSmilesConverter.php +++ b/src/CactusSmilesConverter.php @@ -6,55 +6,26 @@ use Condense\Database; use \GuzzleHttp\Client; /** - * Provides a conversion service between compound names and SMILES strings. + * Provides a conversion service between compound names and SMILES strings that uses a dedicated lookup service. * * @author Saul Johnson * @since 06/08/2017 * @package Avogadrio */ -class CactusSmilesConverter +class CactusSmilesConverter extends SmilesConverter { - /** - * The compound name/SMILES cache database. - * - * @var Database - */ - private $db; - /** * Initializes a new instance of a conversion service between compound names and SMILES strings. * - * @param Database $db the database in which to cache lookup results + * @param Database $db the database in which to cache lookup results + * @param SmilesConverter $fallback the conversion service to fall back to in case of error */ - public function __construct($db = null) { - $this->db = $db; + public function __construct($db = null, $fallback = null) { + parent::__construct($db, $fallback); } /** - * Gets whether or not the API result cache is enabled. - * - * @return bool true if the cache is enabled, otherwise false - */ - public function isCacheEnabled() { - return $this->db !== null; - } - - /** - * Attempts to fix any errors in a SMILES string and returns the result. - * - * @param string $smiles the SMILES string to fix - * @return string the fixed SMILES string - */ - private static function fixSmiles($smiles) { - $output = str_replace('|', '', $smiles); // Zap vertical bars. - return $output; - } - - /** - * Converts a compound name to SMILES notation. - * - * @param string $name the name of the compound - * @return null|string the SMILES notation for the named compound or null if not found + * @inheritdoc */ public function nameToSmiles($name) { @@ -62,11 +33,9 @@ class CactusSmilesConverter $encoded = rawurlencode($name); // Check if we've got the name cached already. - if ($this->isCacheEnabled()) { - $cachedSmiles = $this->db->get('smiles', 'name', $encoded); - if ($cachedSmiles !== null) { - return self::fixSmiles($cachedSmiles); - } + $cached = $this->getIfCachedAndValid($encoded); + if ($cached !== null) { + return $cached; } // Convert chemical name to SMILES if we can using API. @@ -76,13 +45,12 @@ class CactusSmilesConverter // If request was successful. if ($response->getStatusCode() === 200) { $smiles = $response->getBody()->getContents(); - if ($this->isCacheEnabled()) { - $this->db->insert(['name' => $encoded, 'smiles' => $smiles]); // Cache name for future. + if (self::isValidSmiles($smiles)) { + $this->cache($encoded, $smiles); + return $smiles; } - return self::fixSmiles($smiles); } - - // Request failed. - return null; + + return $this->fallback($name); // Fall back. } } diff --git a/src/SmilesConverter.php b/src/SmilesConverter.php new file mode 100644 index 0000000..aaa0ecf --- /dev/null +++ b/src/SmilesConverter.php @@ -0,0 +1,111 @@ + + * @since 27/08/2017 + * @package Avogadrio + */ +abstract class SmilesConverter +{ + /** + * The compound name/SMILES cache database. + * + * @var Database + */ + private $db; + + /** + * The conversion service to fall back to in case of error. + * + * @var SmilesConverter + */ + private $fallback; + + /** + * Initializes a new instance of a conversion service between compound names and SMILES strings. + * + * @param Database $db the database in which to cache lookup results + * @param SmilesConverter $fallback the conversion service to fall back to in case of error + */ + protected function __construct($db, $fallback) { + $this->db = $db; + $this->fallback = $fallback; + } + + /** + * Gets whether or not the API result cache is enabled. + * + * @return bool true if the cache is enabled, otherwise false + */ + public function isCacheEnabled() { + return $this->db !== null; + } + + /** + * Attempts to fix any errors in a SMILES string and returns the result. + * + * @param string $smiles the SMILES string to fix + * @return string the fixed SMILES string + */ + protected static function fixSmiles($smiles) { + $output = str_replace('|', '', $smiles); // Zap vertical bars. + return $output; + } + + /** + * Returns true if a given SMILES string is valid, otherwise returns false. + * + * @param string $smiles the SMILES string to check + * @return bool true if the string was valid, otherwise false + */ + protected static function isValidSmiles($smiles) { + return preg_match('/^[a-zA-Z0-9@%=\\\\\/\[\]\.\+\-\_\*\(\)]+$/', $smiles) ? true : false; + } + + /** + * @param $name + * @return string + */ + protected function getIfCachedAndValid($name) { + if ($this->isCacheEnabled()) { + $cachedSmiles = $this->db->get('smiles', 'name', $name); + if ($cachedSmiles !== null && self::isValidSmiles($cachedSmiles)) { + return $cachedSmiles; + } + } + return null; + } + + protected function cache($name, $smiles) { + if ($this->isCacheEnabled()) { + $this->db->insert(['name' => $name, 'smiles' => $smiles]); // Cache name for future. + } + } + + /** + * Falls back to another SMILES conversion service. + * + * @param string $name the compound name to convert + * @return null|string the converted compound name or null if not found + */ + protected function fallback($name) { + if ($this->fallback === null) { + return null; + } + return $this->fallback->nameToSmiles($name); + } + + /** + * Converts a compound name to SMILES notation. + * + * @param string $name the name of the compound + * @return null|string the SMILES notation for the named compound or null if not found + */ + public abstract function nameToSmiles($name); +} diff --git a/src/WikipediaSmilesConverter.php b/src/WikipediaSmilesConverter.php new file mode 100644 index 0000000..d894354 --- /dev/null +++ b/src/WikipediaSmilesConverter.php @@ -0,0 +1,70 @@ + + * @since 27/08/2017 + * @package Avogadrio + */ +class WikipediaSmilesConverter extends SmilesConverter +{ + /** + * Initializes a new instance of a conversion service between compound names and SMILES strings. + * + * @param Database $db the database in which to cache lookup results + * @param SmilesConverter $fallback the conversion service to fall back to in case of error + */ + public function __construct($db = null, $fallback = null) { + parent::__construct($db, $fallback); + } + + /** + * @inheritdoc + */ + public function nameToSmiles($name) + { + // Sanitize name for URLs. + $encoded = str_replace(' ', '_', $name); + + // Check if we've got the name cached already. + $cached = $this->getIfCachedAndValid($encoded); + if ($cached !== null) { + return $cached; + } + + // Get Wikipedia page for compound. + $client = new Client(); + $response = $client->request('GET', "https://en.wikipedia.org/wiki/$encoded"); + + // If there is a page. + if ($response->getStatusCode() === 200) { + + // Parse page into DOM. + $page = $response->getBody()->getContents(); + $dom = pQuery::parseStr($page); + + // Get all hyperlinks. + $links = $dom->select('a'); + + // Look for the SMILES hyperlink. + foreach ($links as $link) { + if (strstr($link->text(), 'SMILES')) { + $smiles = trim($link->parent->getNextSibling()->text()); // Get actual SMILES string. + if (self::isValidSmiles($smiles)) { + $this->cache($encoded, $smiles); + return $smiles; + } + } + } + } + + return $this->fallback($name); // Fall back. + } +} diff --git a/web/index.php b/web/index.php index d2cae68..019191c 100644 --- a/web/index.php +++ b/web/index.php @@ -9,6 +9,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Condense\Database; use Avogadrio\CactusSmilesConverter; +use Avogadrio\WikipediaSmilesConverter; use Avogadrio\MoleculeRenderer; $app = new Silex\Application(); @@ -20,7 +21,7 @@ $app = new Silex\Application(); $config = Spyc::YAMLLoad(__DIR__ . '/../config/config.yaml'); // Services. -$smilesConverter = new CactusSmilesConverter(new Database('names', __DIR__ . '/../db')); +$smilesConverter = new CactusSmilesConverter(new Database('names', __DIR__ . '/../db'), new WikipediaSmilesConverter()); $moleculeRenderer = new MoleculeRenderer($config['sourire_service']); $moleculeRenderer->setRenderChiralLabels(false); // Disable chiral labels.