Added support for the WEBSERVICE function (#1409)

Co-authored-by: Paul Kievits <kievits@rsm.nl>
This commit is contained in:
paulkned 2020-06-29 03:17:58 +02:00 committed by GitHub
parent e74ef3a802
commit 7f23ccb69d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 471 additions and 2 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Add Cell Address Helper to provide conversions between the R1C1 and A1 address formats [#1558](https://github.com/PHPOffice/PhpSpreadsheet/pull/1558) - Add Cell Address Helper to provide conversions between the R1C1 and A1 address formats [#1558](https://github.com/PHPOffice/PhpSpreadsheet/pull/1558)
- Add ability to edit Html/Pdf before saving [#1499](https://github.com/PHPOffice/PhpSpreadsheet/pull/1499) - Add ability to edit Html/Pdf before saving [#1499](https://github.com/PHPOffice/PhpSpreadsheet/pull/1499)
- Add ability to set codepage explicitly for BIFF5 [#1018](https://github.com/PHPOffice/PhpSpreadsheet/issues/1018) - Add ability to set codepage explicitly for BIFF5 [#1018](https://github.com/PHPOffice/PhpSpreadsheet/issues/1018)
- Added support for the WEBSERVICE function
### Fixed ### Fixed

View File

@ -56,7 +56,8 @@
"maennchen/zipstream-php": "^2.1", "maennchen/zipstream-php": "^2.1",
"markbaker/complex": "^1.4", "markbaker/complex": "^1.4",
"markbaker/matrix": "^1.2", "markbaker/matrix": "^1.2",
"psr/simple-cache": "^1.0" "psr/simple-cache": "^1.0",
"guzzlehttp/guzzle": "^7.0"
}, },
"require-dev": { "require-dev": {
"dompdf/dompdf": "^0.8.5", "dompdf/dompdf": "^0.8.5",

294
composer.lock generated
View File

@ -4,8 +4,211 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "4103c8180a2e28881d3dbb45e835e863", "content-hash": "b7ea4dea7ce2e1c2299029fe978d2173",
"packages": [ "packages": [
{
"name": "guzzlehttp/guzzle",
"version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/2d9d3c186a6637a43193e66b097c50e4451eaab2",
"reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1",
"php": "^7.2.5",
"psr/http-client": "^1.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.0",
"ext-curl": "*",
"php-http/client-integration-tests": "dev-phpunit8",
"phpunit/phpunit": "^8.5.5",
"psr/log": "^1.1"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.0-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"time": "2020-06-27T10:33:25+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "239400de7a173fe9901b9ac7c06497751f00727a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
"reference": "239400de7a173fe9901b9ac7c06497751f00727a",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-zlib": "*",
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"suggest": {
"zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2019-07-01T23:21:34+00:00"
},
{ {
"name": "maennchen/zipstream-php", "name": "maennchen/zipstream-php",
"version": "2.1.0", "version": "2.1.0",
@ -277,6 +480,55 @@
], ],
"time": "2020-02-14T08:15:52+00:00" "time": "2020-02-14T08:15:52+00:00"
}, },
{
"name": "psr/http-client",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "496a823ef742b632934724bf769560c2a5c7c44e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/496a823ef742b632934724bf769560c2a5c7c44e",
"reference": "496a823ef742b632934724bf769560c2a5c7c44e",
"shasum": ""
},
"require": {
"php": "^7.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"time": "2018-10-30T23:29:13+00:00"
},
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.0.1",
@ -375,6 +627,46 @@
], ],
"time": "2017-10-23T01:57:42+00:00" "time": "2017-10-23T01:57:42+00:00"
}, },
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"time": "2019-03-08T08:55:37+00:00"
},
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.17.1", "version": "v1.17.1",

View File

@ -2184,6 +2184,11 @@ class Calculation
'functionCall' => [LookupRef::class, 'VLOOKUP'], 'functionCall' => [LookupRef::class, 'VLOOKUP'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'WEBSERVICE' => [
'category' => Category::CATEGORY_WEB,
'functionCall' => [Web::class, 'WEBSERVICE'],
'argumentCount' => '1',
],
'WEEKDAY' => [ 'WEEKDAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME, 'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTime::class, 'WEEKDAY'], 'functionCall' => [DateTime::class, 'WEEKDAY'],

View File

@ -16,4 +16,5 @@ abstract class Category
const CATEGORY_MATH_AND_TRIG = 'Math and Trig'; const CATEGORY_MATH_AND_TRIG = 'Math and Trig';
const CATEGORY_STATISTICAL = 'Statistical'; const CATEGORY_STATISTICAL = 'Statistical';
const CATEGORY_TEXT_AND_DATA = 'Text and Data'; const CATEGORY_TEXT_AND_DATA = 'Text and Data';
const CATEGORY_WEB = 'Web';
} }

View File

@ -0,0 +1,53 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use GuzzleHttp\Psr7\Request;
use PhpOffice\PhpSpreadsheet\Settings;
use Psr\Http\Client\ClientExceptionInterface;
class Web
{
/**
* WEBSERVICE.
*
* Returns data from a web service on the Internet or Intranet.
*
* Excel Function:
* Webservice(url)
*
* @return string the output resulting from a call to the webservice
*/
public static function WEBSERVICE(string $url)
{
$url = trim($url);
if (strlen($url) > 2048) {
return Functions::VALUE(); // Invalid URL length
}
if (!preg_match('/^http[s]?:\/\//', $url)) {
return Functions::VALUE(); // Invalid protocol
}
// Get results from the the webservice
$client = Settings::getHttpClient();
$request = new Request('GET', $url);
try {
$response = $client->sendRequest($request);
} catch (ClientExceptionInterface $e) {
return Functions::VALUE(); // cURL error
}
if ($response->getStatusCode() != 200) {
return Functions::VALUE(); // cURL error
}
$output = (string) $response->getBody();
if (strlen($output) > 32767) {
return Functions::VALUE(); // Output not a string or too long
}
return $output;
}
}

View File

@ -2,9 +2,11 @@
namespace PhpOffice\PhpSpreadsheet; namespace PhpOffice\PhpSpreadsheet;
use GuzzleHttp\Client;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer; use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer;
use PhpOffice\PhpSpreadsheet\Collection\Memory; use PhpOffice\PhpSpreadsheet\Collection\Memory;
use Psr\Http\Client\ClientInterface;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
class Settings class Settings
@ -42,6 +44,13 @@ class Settings
*/ */
private static $cache; private static $cache;
/**
* The HTTP client implementation to be used for network request.
*
* @var ClientInterface
*/
private static $client;
/** /**
* Set the locale code to use for formula translations and any special formatting. * Set the locale code to use for formula translations and any special formatting.
* *
@ -156,4 +165,24 @@ class Settings
return self::$cache; return self::$cache;
} }
/**
* Set the HTTP client implementation to be used for network request.
*/
public static function setHttpClient(ClientInterface $httpClient): void
{
self::$client = $httpClient;
}
/**
* Get the HTTP client implementation to be used for network request.
*/
public static function getHttpClient(): ClientInterface
{
if (!self::$client) {
self::$client = new Client();
}
return self::$client;
}
} }

View File

@ -0,0 +1,54 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Web;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Web;
use PhpOffice\PhpSpreadsheet\Settings;
use PHPUnit\Framework\TestCase;
class WebServiceTest extends TestCase
{
protected static $client;
public static function setUpBeforeClass(): void
{
// Prevent URL requests being sent out
$mock = new MockHandler([
new ClientException('This is not a valid URL', new Request('GET', 'test'), new Response()),
new ConnectException('This is a 404 error', new Request('GET', 'test')),
new Response('200', [], str_repeat('a', 40000)),
new Response('200', [], 'This is a test'),
]);
$handlerStack = HandlerStack::create($mock);
self::$client = new Client(['handler' => $handlerStack]);
}
protected function setUp(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
/**
* @dataProvider providerWEBSERVICE
*/
public function testWEBSERVICE(string $expectedResult, string $url): void
{
Settings::setHttpClient(self::$client);
$result = Web::WEBSERVICE($url);
self::assertEquals($expectedResult, $result);
}
public function providerWEBSERVICE(): array
{
return require 'tests/data/Calculation/Web/WEBSERVICE.php';
}
}

View File

@ -127,6 +127,11 @@ Excel Function | PhpSpreadsheet Function
Excel Function | PhpSpreadsheet Function Excel Function | PhpSpreadsheet Function
--------------------|------------------------------------------- --------------------|-------------------------------------------
## CATEGORY_WEB
Excel Function | PhpSpreadsheet Function
--------------------|-------------------------------------------
EXPECTED EXPECTED
], ],

View File

@ -0,0 +1,28 @@
<?php
return [
[
'#VALUE!',
'http://www.thisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolorthisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolorthisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolorthisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolorthisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolorthisurlisfartoolongLoremipsumdolorsitametconsecteturadipiscingelitAliquamimperdietmetusurnasedaliquampurusdapibusefficiturQuisqueatullamcorpermaurisacmattisanteDonecsagittisauguenullaegeinterduurnapharetrautQuisquealectusvelnisivolutpatpharetraSuspendisseconvallisvulputateblanditClassaptenttacitisociosquadlitoratorquentperconubianostraperinceptoshimenaeosProinjustdiampulvinaracjustoauctorimperdietsuscipitestEtiamacmaximusmassasitametvulputatedolor.com',
],
[
'#VALUE!',
'ftp://www.bla.com',
],
[
'#VALUE!',
'http://notevenanurl',
],
[
'#VALUE!',
'http://www.example1.com',
],
[
'#VALUE!',
'http://www.example2.com',
],
[
'This is a test',
'http://www.example3.com',
],
];