Skip to content

Commit

Permalink
Use the same verification method for prefixes in RdfNamespace::get an…
Browse files Browse the repository at this point in the history
…d ::set (ported expand too) (#33)

* use the same verification method for RdfNamespace::get and ::set

fixes #32

* fixed some coding style isses in RdfNamespace

* added further throws information to function header

* allow deletion of empty namespace, because it is possible to create it in the first place

* port expand method too; small code niceups

* added a test for Resource::type + hyphen; fixed CS issues
  • Loading branch information
k00ni authored Sep 13, 2023
1 parent 899d102 commit da4d8bf
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 85 deletions.
154 changes: 76 additions & 78 deletions lib/RdfNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ class RdfNamespace
'xsd' => 'http://www.w3.org/2001/XMLSchema#',
];

private static $namespaces;
/**
* @var array<string,string>|null array with prefixes as key and related IRI as value
*/
private static array|null $namespaces = null;

private static $default;

Expand All @@ -118,7 +121,7 @@ class RdfNamespace
/**
* Return all the namespaces registered
*
* @return array associative array of all the namespaces
* @return array<string,string> Associative array of all the namespaces. Key is prefix, value is long URI.
*/
public static function namespaces()
{
Expand All @@ -138,6 +141,50 @@ public static function resetNamespaces()
self::$namespaces = self::$initial_namespaces;
}

/**
* @param string $prefix
*
* @throws \InvalidArgumentException if prefix is not a string
* @throws \InvalidArgumentException if prefix does not match RDFXML-QName specification
* @throws \LogicException if preg_match returned false when checking the given prefix
*/
private static function verifyPrefix($prefix): void
{
if (\is_string($prefix) && '' !== $prefix) {
// prefix ::= Name minus ":" // see: http://www.w3.org/TR/REC-xml-names/#NT-NCName
// Name ::= NameStartChar (NameChar)* // see: http://www.w3.org/TR/REC-xml/#NT-Name
// NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] |
// [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] |
// [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
// NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]

$_name_start_char =
'A-Z_a-z\xc0-\xD6\xd8-\xf6\xf8-\xff\x{0100}-\x{02ff}\x{0370}-\x{037d}'.
'\x{037F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}'.
'\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';

$_name_char =
$_name_start_char.
'\-.0-9\xb7\x{0300}-\x{036f}\x{203f}-\x{2040}';

$regex = "#^[{$_name_start_char}]{1}[{$_name_char}]{0,}$#u";

$match_result = preg_match($regex, $prefix);

if (false === $match_result) {
throw new \LogicException('regexp error');
}

if (0 === $match_result) {
throw new \InvalidArgumentException("\$prefix should match RDFXML-QName specification. got: {$prefix}");
}
} elseif ('' === $prefix) {
// empty prefix
} else {
throw new \InvalidArgumentException('$prefix should be a string and cannot be null or empty');
}
}

/**
* Return a namespace given its prefix.
*
Expand All @@ -149,15 +196,7 @@ public static function resetNamespaces()
*/
public static function get($prefix)
{
// TODO fix PHPStan error by rethinking datatype of parameter(s)
// @phpstan-ignore-next-line
if (!\is_string($prefix) || null === $prefix) {
throw new \InvalidArgumentException('$prefix should be a string and cannot be null or empty');
}

if (preg_match('/\W/', $prefix)) {
throw new \InvalidArgumentException('$prefix should only contain alpha-numeric characters');
}
self::verifyPrefix($prefix);

$prefix = strtolower($prefix);
$namespaces = self::namespaces();
Expand All @@ -180,54 +219,18 @@ public static function get($prefix)
*/
public static function set($prefix, $long)
{
// TODO fix PHPStan error by rethinking datatype of parameter(s)
// @phpstan-ignore-next-line
if (!\is_string($prefix) || null === $prefix) {
throw new \InvalidArgumentException('$prefix should be a string and cannot be null or empty');
}

if ('' !== $prefix) {
// prefix ::= Name minus ":" // see: http://www.w3.org/TR/REC-xml-names/#NT-NCName
// Name ::= NameStartChar (NameChar)* // see: http://www.w3.org/TR/REC-xml/#NT-Name
// NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] |
// [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] |
// [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
// NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
self::verifyPrefix($prefix);

$_name_start_char =
'A-Z_a-z\xc0-\xD6\xd8-\xf6\xf8-\xff\x{0100}-\x{02ff}\x{0370}-\x{037d}'.
'\x{037F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}'.
'\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
if (\is_string($long) && '' !== $long) {
$prefix = strtolower($prefix);

$_name_char =
$_name_start_char.
'\-.0-9\xb7\x{0300}-\x{036f}\x{203f}-\x{2040}';

$regex = "#^[{$_name_start_char}]{1}[{$_name_char}]{0,}$#u";

$match_result = preg_match($regex, $prefix);

if (false === $match_result) {
throw new \LogicException('regexp error');
}

if (0 === $match_result) {
throw new \InvalidArgumentException("\$prefix should match RDFXML-QName specification. got: {$prefix}");
}
}
$namespaces = self::namespaces();
$namespaces[$prefix] = $long;

// TODO fix PHPStan error by rethinking datatype of parameter(s)
// @phpstan-ignore-next-line
if (!\is_string($long) || null === $long || '' === $long) {
self::$namespaces = $namespaces;
} else {
throw new \InvalidArgumentException('$long should be a string and cannot be null or empty');
}

$prefix = strtolower($prefix);

$namespaces = self::namespaces();
$namespaces[$prefix] = $long;

self::$namespaces = $namespaces;
}

/**
Expand Down Expand Up @@ -284,11 +287,7 @@ public static function setDefault($namespace)
*/
public static function delete($prefix)
{
// TODO fix PHPStan error by rethinking datatype of parameter(s)
// @phpstan-ignore-next-line
if (!\is_string($prefix) || null === $prefix || '' === $prefix) {
throw new \InvalidArgumentException('$prefix should be a string and cannot be null or empty');
}
self::verifyPrefix($prefix);

$prefix = strtolower($prefix);
self::namespaces(); // make sure, that self::$namespaces is initialized
Expand Down Expand Up @@ -320,7 +319,7 @@ public static function reset()
* @param string|\EasyRdf\Resource $uri The full URI (eg 'http://xmlns.com/foaf/0.1/name')
* @param bool $createNamespace If true, a new namespace will be created
*
* @return array|null The split URI (eg 'foaf', 'name') or null
* @return array{string,string}|null The split URI (eg 'foaf', 'name') or null
*
* @throws \InvalidArgumentException
*/
Expand All @@ -335,7 +334,8 @@ public static function splitUri($uri, $createNamespace = false)

if (\is_object($uri) && ($uri instanceof Resource)) {
$uri = $uri->getUri();
} elseif (!\is_string($uri)) {
// @phpstan-ignore-next-line
} elseif (false === \is_string($uri)) {
throw new \InvalidArgumentException('$uri should be a string or EasyRdf\Resource');
}

Expand Down Expand Up @@ -413,27 +413,25 @@ public static function shorten($uri, $createNamespace = false)
*
* @return string The full URI (eg 'http://xmlns.com/foaf/0.1/name')
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException if $shortUri is not a string or null or empty string
*/
public static function expand($shortUri)
{
if (!\is_string($shortUri) || '' === $shortUri) {
throw new \InvalidArgumentException('$shortUri should be a string and cannot be null or empty');
}

if ('a' === $shortUri) {
$namespaces = self::namespaces();

return $namespaces['rdf'].'type';
} elseif (preg_match('/^(\w+?):([\w\-]+)$/', $shortUri, $matches)) {
$long = self::get($matches[1]);
if ($long) {
return $long.$matches[2];
if (\is_string($shortUri) && '' !== $shortUri) {
if ('a' === $shortUri) {
return self::namespaces()['rdf'].'type';
} elseif (preg_match('/^([\w\-]+?):([\w\-]+)$/', $shortUri, $matches)) {
$long = self::get($matches[1]);
if ($long) {
return $long.$matches[2];
}
} elseif (1 === preg_match('/^([\w\-]+)$/', $shortUri) && isset(self::$default)) {
return self::$default.$shortUri;
}
} elseif (preg_match('/^(\w+)$/', $shortUri) && isset(self::$default)) {
return self::$default.$shortUri;
}

return $shortUri;
return $shortUri;
} else {
throw new \InvalidArgumentException('$shortUri should be a string and cannot be null or empty');
}
}
}
36 changes: 29 additions & 7 deletions tests/EasyRdf/RdfNamespaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ public function testGetNonStringNamespace()
public function testGetNonAlphanumeric()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage(
'$prefix should only contain alpha-numeric characters'
);
$this->expectExceptionMessage('$prefix should match RDFXML-QName specification. got: /K.O/');
RdfNamespace::get('/K.O/');
}

Expand Down Expand Up @@ -236,11 +234,14 @@ public function testDeleteNamespace()

public function testDeleteEmptyNamespace()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage(
'$prefix should be a string and cannot be null or empty'
);
$ns = 'http://empty/namespace';
RdfNamespace::set('', $ns);

$this->assertEquals($ns, RdfNamespace::get(''));

RdfNamespace::delete('');

$this->assertNull(RdfNamespace::get(''));
}

public function testDeleteNullNamespace()
Expand Down Expand Up @@ -661,6 +662,16 @@ public function testExpandNonString()
RdfNamespace::expand($this);
}

/**
* @see https://github.com/sweetrdf/easyrdf/issues/32#issuecomment-1678073874
*/
public function testExpandStringContainsHyphen()
{
RdfNamespace::set('foo-bar', 'http://long/uri/');

$this->assertEquals('http://long/uri/12', RdfNamespace::expand('foo-bar:12'));
}

/**
* @see https://github.com/easyrdf/easyrdf/issues/185
*/
Expand All @@ -684,6 +695,17 @@ public function testShortNamespace()
);
}

/**
* @see https://github.com/sweetrdf/easyrdf/issues/32
*/
public function testIssue32HyphenInName()
{
$url = 'http://example.org/dash#';

RdfNamespace::set('foo-bar', $url);
$this->assertSame($url, RdfNamespace::get('foo-bar'));
}

/**
* URIs with fragments can only be shortened if the '#' character
* is part of the prefix. `prefix:[...]#fragment` is not a valid result
Expand Down
18 changes: 18 additions & 0 deletions tests/EasyRdf/ResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,24 @@ public function testTypeAsResource()
);
}

/**
* Test type() together with a prefix that contains a hyphen.
*
* @see https://github.com/sweetrdf/easyrdf/issues/32#issuecomment-1678073874
*/
public function testTypeWithHyphen()
{
RdfNamespace::set('foo-bar', 'http://foo/bar#');

$this->graph = new Graph();
$this->type = $this->graph->resource('foo-bar:Person');
$this->resource = $this->graph->resource('http://example.com/#me');

$this->graph->set($this->resource, 'rdf:type', $this->type);

$this->assertEquals('foo-bar:Person', $this->resource->type());
}

public function testIsA()
{
$this->setupTestGraph();
Expand Down

0 comments on commit da4d8bf

Please sign in to comment.