regular - Validación/regex de PHP para URL
preg_match php w3school (18)
Aquí hay una clase simple para la Validación de URL que usa RegEx y luego hace una referencia cruzada del dominio contra los populares servidores RBL (Realtime Blackhole Lists):
Instalar:
require ''URLValidation.php'';
Uso:
require ''URLValidation.php'';
$urlVal = new UrlValidation(); //Create Object Instance
Agregue una URL como el parámetro del método de domain()
y verifique la devolución.
$urlArray = [''http://www.bokranzr.com/test.php?test=foo&test=dfdf'', ''https://en-gb.facebook.com'', ''https://www.google.com''];
foreach ($urlArray as $k=>$v) {
echo var_dump($urlVal->domain($v)) . '' URL: '' . $v . ''<br>'';
}
Salida:
bool(false) URL: http://www.bokranzr.com/test.php?test=foo&test=dfdf
bool(true) URL: https://en-gb.facebook.com
bool(true) URL: https://www.google.com
Como puede ver arriba, www.bokranzr.com figura como sitio web malicioso a través de un RBL, por lo que el dominio se devolvió como falso.
He estado buscando una expresión regular simple para las URL, ¿alguien tiene una a mano que funcione bien? No encontré uno con las clases de validación de zend framework y he visto varias implementaciones.
Gracias
En caso de que quiera saber si la URL realmente existe:
function url_exist($url){//se passar a URL existe
$c=curl_init();
curl_setopt($c,CURLOPT_URL,$url);
curl_setopt($c,CURLOPT_HEADER,1);//get the header
curl_setopt($c,CURLOPT_NOBODY,1);//and *only* get the header
curl_setopt($c,CURLOPT_RETURNTRANSFER,1);//get the response as a string from curl_exec(), rather than echoing it
curl_setopt($c,CURLOPT_FRESH_CONNECT,1);//don''t use a cached version of the url
if(!curl_exec($c)){
//echo $url.'' inexists'';
return false;
}else{
//echo $url.'' exists'';
return true;
}
//$httpcode=curl_getinfo($c,CURLINFO_HTTP_CODE);
//return ($httpcode<400);
}
Esta es la forma en que lo hice. Pero quiero mencionar que no estoy tan seguro sobre la expresión regular. Pero debería funcionar tú :)
$pattern = "#((http|https)://(/S*?/./S*?))(/s|/;|/)|/]|/[|/{|/}|,|”|/"|''|:|/<|$|/./s)#i";
$text = preg_replace_callback($pattern,function($m){
return "<a href=/"$m[1]/" target=/"_blank/">$m[1]</a>$m[4]";
},
$text);
De esta forma, no necesitará el marcador eval en su patrón.
Espero eso ayude :)
Hay una función nativa de PHP para eso:
$url = ''http://www.yoururl.co.uk/sub1/sub2/?param=1¶m2/'';
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
// Wrong
}
else {
// Valid
}
Devuelve los datos filtrados, o FALSE si el filtro falla.
He encontrado que este es el más útil para hacer coincidir una URL.
^(https?:////)?([/da-z/.-]+)/.([a-z/.]{2,6})([///w /.-]*)*//?$
Inspirado en esta pregunta de .NET y en este artículo al que se hace referencia de esa pregunta, existe este validador de URI (URI significa que valida tanto la URL como la URN).
if( ! preg_match( "/^([a-z][a-z0-9+.-]*):(?://////((?:(?=((?:[a-z0-9-._~!$&''()*+,;=:]|%[0-9A-F]{2})*))(//3)@)?(?=(//[[0-9A-F:.]{2,}//]|(?:[a-z0-9-._~!$&''()*+,;=]|%[0-9A-F]{2})*))//5(?::(?=(//d*))//6)?)(///(?=((?:[a-z0-9-._~!$&''()*+,;=:@///]|%[0-9A-F]{2})*))//8)?|(///?(?!///)(?=((?:[a-z0-9-._~!$&''()*+,;=:@///]|%[0-9A-F]{2})*))//10)?)(?://?(?=((?:[a-z0-9-._~!$&''()*+,;=:@///?]|%[0-9A-F]{2})*))//11)?(?:#(?=((?:[a-z0-9-._~!$&''()*+,;=:@///?]|%[0-9A-F]{2})*))//12)?$/i", $uri ) )
{
throw new /RuntimeException( "URI has not a valid format." );
}
He probado con éxito esta función dentro de un ValueObject que realicé con el nombre Uri
y que fue probado por UriTest
.
UriTest.php (contiene casos válidos y no válidos para URL y URN)
<?php
declare( strict_types = 1 );
namespace XaviMontero/ThrasherPortage/Tests/Tour;
use XaviMontero/ThrasherPortage/Tour/Uri;
class UriTest extends /PHPUnit_Framework_TestCase
{
private $sut;
public function testCreationIsOfProperClassWhenUriIsValid()
{
$sut = new Uri( ''http://example.com'' );
$this->assertInstanceOf( ''XaviMontero//ThrasherPortage//Tour//Uri'', $sut );
}
/**
* @dataProvider urlIsValidProvider
* @dataProvider urnIsValidProvider
*/
public function testGetUriAsStringWhenUriIsValid( string $uri )
{
$sut = new Uri( $uri );
$actual = $sut->getUriAsString();
$this->assertInternalType( ''string'', $actual );
$this->assertEquals( $uri, $actual );
}
public function urlIsValidProvider()
{
return
[
[ ''http://example-server'' ],
[ ''http://example.com'' ],
[ ''http://example.com/'' ],
[ ''http://subdomain.example.com/path/?parameter1=value1¶meter2=value2'' ],
[ ''random-protocol://example.com'' ],
[ ''http://example.com:80'' ],
[ ''http://example.com?no-path-separator'' ],
[ ''http://example.com/pa%20th/'' ],
[ ''ftp://example.org/resource.txt'' ],
[ ''file://../../../relative/path/needs/protocol/resource.txt'' ],
[ ''http://example.com/#one-fragment'' ],
[ ''http://example.edu:8080#one-fragment'' ],
];
}
public function urnIsValidProvider()
{
return
[
[ ''urn:isbn:0-486-27557-4'' ],
[ ''urn:example:mammal:monotreme:echidna'' ],
[ ''urn:mpeg:mpeg7:schema:2001'' ],
[ ''urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66'' ],
[ ''rare-urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66'' ],
[ ''urn:FOO:a123,456'' ]
];
}
/**
* @dataProvider urlIsNotValidProvider
* @dataProvider urnIsNotValidProvider
*/
public function testCreationThrowsExceptionWhenUriIsNotValid( string $uri )
{
$this->expectException( ''RuntimeException'' );
$this->sut = new Uri( $uri );
}
public function urlIsNotValidProvider()
{
return
[
[ ''only-text'' ],
[ ''http//missing.colon.example.com/path/?parameter1=value1¶meter2=value2'' ],
[ ''missing.protocol.example.com/path/'' ],
[ ''http://example.com//bad-separator'' ],
[ ''http://example.com|bad-separator'' ],
[ ''ht tp://example.com'' ],
[ ''http://exampl e.com'' ],
[ ''http://example.com/pa th/'' ],
[ ''../../../relative/path/needs/protocol/resource.txt'' ],
[ ''http://example.com/#two-fragments#not-allowed'' ],
[ ''http://example.edu:portMustBeANumber#one-fragment'' ],
];
}
public function urnIsNotValidProvider()
{
return
[
[ ''urn:mpeg:mpeg7:sch ema:2001'' ],
[ ''urn|mpeg:mpeg7:schema:2001'' ],
[ ''urn?mpeg:mpeg7:schema:2001'' ],
[ ''urn%mpeg:mpeg7:schema:2001'' ],
[ ''urn#mpeg:mpeg7:schema:2001'' ],
];
}
}
Uri.php (Objeto de valor)
<?php
declare( strict_types = 1 );
namespace XaviMontero/ThrasherPortage/Tour;
class Uri
{
/** @var string */
private $uri;
public function __construct( string $uri )
{
$this->assertUriIsCorrect( $uri );
$this->uri = $uri;
}
public function getUriAsString()
{
return $this->uri;
}
private function assertUriIsCorrect( string $uri )
{
// https://.com/questions/30847/regex-to-validate-uris
// http://snipplr.com/view/6889/regular-expressions-for-uri-validationparsing/
if( ! preg_match( "/^([a-z][a-z0-9+.-]*):(?://////((?:(?=((?:[a-z0-9-._~!$&''()*+,;=:]|%[0-9A-F]{2})*))(//3)@)?(?=(//[[0-9A-F:.]{2,}//]|(?:[a-z0-9-._~!$&''()*+,;=]|%[0-9A-F]{2})*))//5(?::(?=(//d*))//6)?)(///(?=((?:[a-z0-9-._~!$&''()*+,;=:@///]|%[0-9A-F]{2})*))//8)?|(///?(?!///)(?=((?:[a-z0-9-._~!$&''()*+,;=:@///]|%[0-9A-F]{2})*))//10)?)(?://?(?=((?:[a-z0-9-._~!$&''()*+,;=:@///?]|%[0-9A-F]{2})*))//11)?(?:#(?=((?:[a-z0-9-._~!$&''()*+,;=:@///?]|%[0-9A-F]{2})*))//12)?$/i", $uri ) )
{
throw new /RuntimeException( "URI has not a valid format." );
}
}
}
Ejecución de pruebas de la unidad
Hay 65 afirmaciones en 46 pruebas. Precaución: hay 2 proveedores de datos para expresiones válidas y 2 más para expresiones no válidas. Una es para URL y la otra para URN. Si está utilizando una versión de PhpUnit de v5.6 * o anterior, debe unir los dos proveedores de datos en uno solo.
xavi@bromo:~/custom_www/hello-trip/mutant-migrant$ vendor/bin/phpunit
PHPUnit 5.7.3 by Sebastian Bergmann and contributors.
.............................................. 46 / 46 (100%)
Time: 82 ms, Memory: 4.00MB
OK (46 tests, 65 assertions)
Cobertura de código
Hay un 100% de cobertura de código en este comprobador de URI de muestra.
Lo usé en algunos proyectos, no creo haber tenido problemas, pero estoy seguro de que no es exhaustivo:
$text = preg_replace(
''#((https?|ftp)://(/S*?/./S*?))([/s)/[/]{},;"/':<]|/./s|$)#i'',
"''<a href=/"$1/" target=/"_blank/">$3</a>$4''",
$text
);
La mayoría de la basura al azar al final es para tratar situaciones como http://domain.com.
en una oración (para evitar coincidir con el período posterior). Estoy seguro de que podría limpiarse, pero dado que funcionó. Más o menos lo he copiado de proyecto en proyecto.
No creo que usar expresiones regulares sea algo inteligente en este caso. Es imposible hacer coincidir todas las posibilidades e, incluso si lo hiciste, aún existe la posibilidad de que la URL simplemente no exista.
Aquí hay una manera muy simple de probar si la URL realmente existe y es legible:
if (preg_match("#^https?://.+#", $link) and @fopen($link,"r")) echo "OK";
(Si no hay preg_match
, esto también validaría todos los nombres de archivo en su servidor)
OK, entonces esto es un poco más complejo que una simple expresión regular, pero permite diferentes tipos de URL.
Ejemplos:
- google.com
- www.microsoft.com/
- http://www.yahoo.com/
- https://www.bandcamp.com/artist/#!someone-special !
Todo lo cual debe ser marcado como válido.
function is_valid_url($url) {
// First check: is the url just a domain name? (allow a slash at the end)
$_domain_regex = "|^[A-Za-z0-9-]+(/.[A-Za-z0-9-]+)*(/.[A-Za-z]{2,})/?$|";
if (preg_match($_domain_regex, $url)) {
return true;
}
// Second: Check if it''s a url with a scheme and all
$_regex = ''#^([a-z][/w-]+:(?:/{1,3}|[a-z0-9%])|www/d{0,3}[.]|[a-z0-9./-]+[.][a-z]{2,4}/)(?:[^/s()<>]+|/(([^/s()<>]+|(/([^/s()<>]+/)))*/))$#'';
if (preg_match($_regex, $url, $matches)) {
// pull out the domain name, and make sure that the domain is valid.
$_parts = parse_url($url);
if (!in_array($_parts[''scheme''], array( ''http'', ''https'' )))
return false;
// Check the domain using the regex, stops domains like "-example.com" passing through
if (!preg_match($_domain_regex, $_parts[''host'']))
return false;
// This domain looks pretty valid. Only way to check it now is to download it!
return true;
}
return false;
}
Tenga en cuenta que hay una verificación in_array para los protocolos que desea permitir (actualmente solo http y https están en esa lista).
var_dump(is_valid_url(''google.com'')); // true
var_dump(is_valid_url(''google.com/'')); // true
var_dump(is_valid_url(''http://google.com'')); // true
var_dump(is_valid_url(''http://google.com/'')); // true
var_dump(is_valid_url(''https://google.com'')); // true
Peter''s Regex no me parece correcto por muchas razones. Permite todo tipo de caracteres especiales en el nombre de dominio y no prueba mucho.
La función de Frankie se ve bien para mí y puedes construir una buena expresión regular de los componentes si no quieres una función, así:
^(http://|https://)(([a-z0-9]([-a-z0-9]*[a-z0-9]+)?){1,63}/.)+[a-z]{2,6}
No probado, pero creo que debería funcionar.
Además, la respuesta de Owen tampoco se ve al 100%. Tomé el dominio parte de la expresión regular y lo probé en una herramienta de prueba Regex http://erik.eae.net/playground/regexp/regexp.html
Pongo la siguiente línea:
(/S*?/./S*?)
en la sección "regexp" y en la siguiente línea:
-hello.com
en la sección "texto de muestra".
El resultado permitió que pase el caracter negativo. Porque / S significa cualquier carácter no espacial.
Tenga en cuenta que la expresión regular de Frankie maneja el signo menos porque tiene esta parte para el primer carácter:
[a-z0-9]
Lo cual no permitirá el menos o cualquier otro carácter especial.
Según John Gruber (Daring Fireball):
Regex:
(?i)/b((?:https?://|www/d{0,3}[.]|[a-z0-9./-]+[.][a-z]{2,4}/)(?:[^/s()<>]+|/(([^/s()<>]+|(/([^/s()<>]+/)))*/))+(?:/(([^/s()<>]+|(/([^/s()<>]+/)))*/)|[^/s`!()/[/]{};:''/".,<>?«»“”‘’]))
usando en preg_match ():
preg_match("/(?i)/b((?:https?://|www/d{0,3}[.]|[a-z0-9./-]+[.][a-z]{2,4}/)(?:[^/s()<>]+|/(([^/s()<>]+|(/([^/s()<>]+/)))*/))+(?:/(([^/s()<>]+|(/([^/s()<>]+/)))*/)|[^/s`!()/[/]{};:''/".,<>?«»“”‘’]))/", $url)
Aquí está el patrón de expresiones regulares extendido (con comentarios):
(?xi)
/b
( # Capture 1: entire matched URL
(?:
https?:// # http or https protocol
| # or
www/d{0,3}[.] # "www.", "www1.", "www2." … "www999."
| # or
[a-z0-9./-]+[.][a-z]{2,4}/ # looks like domain name followed by a slash
)
(?: # One or more:
[^/s()<>]+ # Run of non-space, non-()<>
| # or
/(([^/s()<>]+|(/([^/s()<>]+/)))*/) # balanced parens, up to 2 levels
)+
(?: # End with:
/(([^/s()<>]+|(/([^/s()<>]+/)))*/) # balanced parens, up to 2 levels
| # or
[^/s`!()/[/]{};:''".,<>?«»“”‘’] # not a space or one of these punct chars
)
)
Para obtener más información, consulte: http://daringfireball.net/2010/07/improved_regex_for_matching_urls
Según el manual de PHP, parse_url no debe usarse para validar una URL.
Desafortunadamente, parece que filter_var(''example.com'', FILTER_VALIDATE_URL)
no funciona mejor.
Ambos parse_url()
y filter_var()
pasarán URL mal formadas como http://...
Por lo tanto, en este caso, la expresión regular es el mejor método.
Utilicé este con éxito: no recuerdo de dónde lo saqué
$pattern = "//b(?:(?:https?|ftp):////|www/.)[-a-z0-9+&@#//%?=~_|!:,.;]*[-a-z0-9+&@#//%=~_|]/i";
Utilice la función filter_var()
para validar si una cadena es URL o no:
var_dump(filter_var(''example.com'', FILTER_VALIDATE_URL));
Es una mala práctica usar expresiones regulares cuando no es necesario.
EDITAR : tenga cuidado, esta solución no es segura para unicode y no es segura para XSS. Si necesita una validación compleja, quizás sea mejor buscar en otro lado.
Y ahí está tu respuesta =) ¡Trata de romperlo, no puedes!
function link_validate_url($text) {
$LINK_DOMAINS = ''aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local'';
$LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( // @TODO completing letters ...
"æ", // æ
"Æ", // Æ
"À", // À
"à", // à
"Á", // Á
"á", // á
"Â", // Â
"â", // â
"å", // å
"Å", // Å
"ä", // ä
"Ä", // Ä
"Ç", // Ç
"ç", // ç
"Ð", // Ð
"ð", // ð
"È", // È
"è", // è
"É", // É
"é", // é
"Ê", // Ê
"ê", // ê
"Ë", // Ë
"ë", // ë
"Î", // Î
"î", // î
"Ï", // Ï
"ï", // ï
"ø", // ø
"Ø", // Ø
"ö", // ö
"Ö", // Ö
"Ô", // Ô
"ô", // ô
"Õ", // Õ
"õ", // õ
"Œ", // Œ
"œ", // œ
"ü", // ü
"Ü", // Ü
"Ù", // Ù
"ù", // ù
"Û", // Û
"û", // û
"Ÿ", // Ÿ
"ÿ", // ÿ
"Ñ", // Ñ
"ñ", // ñ
"þ", // þ
"Þ", // Þ
"ý", // ý
"Ý", // Ý
"¿", // ¿
)), ENT_QUOTES, ''UTF-8'');
$LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
"ß", // ß
)), ENT_QUOTES, ''UTF-8'');
$allowed_protocols = array(''http'', ''https'', ''ftp'', ''news'', ''nntp'', ''telnet'', ''mailto'', ''irc'', ''ssh'', ''sftp'', ''webcal'');
// Starting a parenthesis group with (?: means that it is grouped, but is not captured
$protocol = ''((?:''. implode("|", $allowed_protocols) .''):////)'';
$authentication = "(?:(?:(?:[/w/./-/+!$&''/(/)*/+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[/w". $LINK_ICHARS ."/./-/+%!$&''/(/)*/+,;=]|%[0-9a-f]{2})*)?)?@)";
$domain = ''(?:(?:[a-z0-9'' . $LINK_ICHARS_DOMAIN . '']([a-z0-9''. $LINK_ICHARS_DOMAIN . ''/-_/[/]])*)(/.(([a-z0-9'' . $LINK_ICHARS_DOMAIN . ''/-_/[/]])+/.)*(''. $LINK_DOMAINS .''|[a-z]{2}))?)'';
$ipv4 = ''(?:[0-9]{1,3}(/.[0-9]{1,3}){3})'';
$ipv6 = ''(?:[0-9a-fA-F]{1,4}(/:[0-9a-fA-F]{1,4}){7})'';
$port = ''(?::([0-9]{1,5}))'';
// Pattern specific to external links.
$external_pattern = ''/^''. $protocol .''?''. $authentication .''?(''. $domain .''|''. $ipv4 .''|''. $ipv6 .'' |localhost)''. $port .''?'';
// Pattern specific to internal links.
$internal_pattern = "/^(?:[a-z0-9". $LINK_ICHARS ."_/-+/[/]]+)";
$internal_pattern_file = "/^(?:[a-z0-9". $LINK_ICHARS ."_/-+/[/]/.]+)$/i";
$directories = "(?://[a-z0-9". $LINK_ICHARS ."_/-/.~+%=&,$''#!():;*@/[/]]*)*";
// Yes, four backslashes == a single backslash.
$query = "(?://?/?([?a-z0-9". $LINK_ICHARS ."+_|/-/.~//////%=&,$''():;*@/[/]{} ]*))";
$anchor = "(?:#[a-z0-9". $LINK_ICHARS ."_/-/.~+%=&,$''():;*@/[/]///?]*)";
// The rest of the path for a standard URL.
$end = $directories .''?''. $query .''?''. $anchor .''?''.''$/i'';
$message_id = ''[^@].*@''. $domain;
$newsgroup_name = ''(?:[0-9a-z+-]*/.)*[0-9a-z+-]*'';
$news_pattern = ''/^news:(''. $newsgroup_name .''|''. $message_id .'')$/i'';
$user = ''[a-zA-Z0-9''. $LINK_ICHARS .''_/-/./+/^!#/$%&*+///=/?/`/|/{/}~/'/[/]]+'';
$email_pattern = ''/^mailto:''. $user .''@''.''(?:''. $domain .''|''. $ipv4 .''|''. $ipv6 .''|localhost)''. $query .''?$/'';
if (strpos($text, ''<front>'') === 0) {
return false;
}
if (in_array(''mailto'', $allowed_protocols) && preg_match($email_pattern, $text)) {
return false;
}
if (in_array(''news'', $allowed_protocols) && preg_match($news_pattern, $text)) {
return false;
}
if (preg_match($internal_pattern . $end, $text)) {
return false;
}
if (preg_match($external_pattern . $end, $text)) {
return false;
}
if (preg_match($internal_pattern_file, $text)) {
return false;
}
return true;
}
Editar:
Como indica la incidence , este código ha sido DEPURADO con el lanzamiento de PHP 5.3.0 (2009-06-30) y debe usarse en consecuencia.
Solo mi granito de arena, pero he desarrollado esta función y la he estado utilizando por un tiempo con éxito. Está bien documentado y separado para que pueda cambiarlo fácilmente.
// Checks if string is a URL
// @param string $url
// @return bool
function isURL($url = NULL) {
if($url==NULL) return false;
$protocol = ''(http://|https://)'';
$allowed = ''([a-z0-9]([-a-z0-9]*[a-z0-9]+)?)'';
$regex = "^". $protocol . // must include the protocol
''('' . $allowed . ''{1,63}/.)+''. // 1 or several sub domains with a max of 63 chars
''[a-z]'' . ''{2,6}''; // followed by a TLD
if(eregi($regex, $url)==true) return true;
else return false;
}
function validateURL($URL) {
$pattern_1 = "/^(http|https|ftp):////(([A-Z0-9][A-Z0-9_-]*)(/.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(/d+))?//?/i";
$pattern_2 = "/^(www)((/.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(/d+))?//?/i";
if(preg_match($pattern_1, $URL) || preg_match($pattern_2, $URL)){
return true;
} else{
return false;
}
}
function is_valid_url ($url="") {
if ($url=="") {
$url=$this->url;
}
$url = @parse_url($url);
if ( ! $url) {
return false;
}
$url = array_map(''trim'', $url);
$url[''port''] = (!isset($url[''port''])) ? 80 : (int)$url[''port''];
$path = (isset($url[''path''])) ? $url[''path''] : '''';
if ($path == '''') {
$path = ''/'';
}
$path .= ( isset ( $url[''query''] ) ) ? "?$url[query]" : '''';
if ( isset ( $url[''host''] ) AND $url[''host''] != gethostbyname ( $url[''host''] ) ) {
if ( PHP_VERSION >= 5 ) {
$headers = get_headers("$url[scheme]://$url[host]:$url[port]$path");
}
else {
$fp = fsockopen($url[''host''], $url[''port''], $errno, $errstr, 30);
if ( ! $fp ) {
return false;
}
fputs($fp, "HEAD $path HTTP/1.1/r/nHost: $url[host]/r/n/r/n");
$headers = fread ( $fp, 128 );
fclose ( $fp );
}
$headers = ( is_array ( $headers ) ) ? implode ( "/n", $headers ) : $headers;
return ( bool ) preg_match ( ''#^HTTP/.*/s+[(200|301|302)]+/s#i'', $headers );
}
return false;
}