Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ParserParameterProcessor to support JSON typed annotation #1891

Merged
merged 1 commit into from Oct 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion i18n/en.json
Expand Up @@ -444,5 +444,6 @@
"smw-datavalue-wikipage-empty": "The wikipage input value is empty (e.g. <code>[[SomeProperty::]], [[]]</code>) and therefore it cannot be used as a name or as part of a query condition.",
"smw-sp-types_ref_rec": "\"$1\" is a [https://www.semantic-mediawiki.org/wiki/Container container] type that allows to record additional information (e.g. provenance data) about a value assignment.",
"smw-datavalue-reference-outputformat": "$1: $2",
"smw-datavalue-reference-invalid-fields-definition": "The [[Special:Types/Reference|Reference]] type expects a list of properties to be declared using the [https://www.semantic-mediawiki.org/wiki/Help:Special_property_Has_fields Has fields] property."
"smw-datavalue-reference-invalid-fields-definition": "The [[Special:Types/Reference|Reference]] type expects a list of properties to be declared using the [https://www.semantic-mediawiki.org/wiki/Help:Special_property_Has_fields Has fields] property.",
"smw-parser-invalid-json-format": "The JSON parser returned with a \"$1\"."
}
19 changes: 2 additions & 17 deletions src/ExtraneousLanguage/LanguageFileContentsReader.php
Expand Up @@ -5,6 +5,7 @@
use RuntimeException;
use Onoi\Cache\Cache;
use Onoi\Cache\NullCache;
use SMW\Libs\ErrorCode;

/**
* @license GNU GPL v2+
Expand Down Expand Up @@ -174,7 +175,7 @@ protected function doReadJsonContentsFromFileBy( $languageCode, $cacheKey ) {
return $contents;
}

throw new RuntimeException( $this->getMessageForJsonErrorCode( json_last_error() ) );
throw new RuntimeException( ErrorCode::getMessageFromJsonErrorCode( json_last_error() ) );
}

private function getFileForLanguageCode( $languageCode ) {
Expand All @@ -188,22 +189,6 @@ private function getFileForLanguageCode( $languageCode ) {
throw new RuntimeException( "Expected a {$file} file" );
}

private function getMessageForJsonErrorCode( $errorCode ) {

$errorMessages = array(
JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch, malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded'
);

return sprintf(
"Expected a JSON compatible format but failed with '%s'",
$errorMessages[$errorCode]
);
}

private function getCacheKeyFrom( $languageCode ) {
return $this->cachePrefix . ':' . $languageCode . ':' . md5( $this->ttl . $this->getModificationTimeByLanguageCode( $languageCode ) );
}
Expand Down
77 changes: 77 additions & 0 deletions src/Libs/ErrorCode.php
@@ -0,0 +1,77 @@
<?php

namespace SMW\Libs;

/**
* Convenience method to retrieved stringified error codes.
*
* @license GNU GPL v2+
* @since 2.5
*
* @author mwjames
*/
class ErrorCode {

/**
* @var array
*/
private static $constants = array();

/**
* @var array
*/
private static $jsonErrors = array();

/**
* @see http://php.net/manual/en/function.json-decode.php
* @since 2.5
*
* @param integer $errorCode
*
* @return string
*/
public static function getStringFromJsonErrorCode( $errorCode ) {

if ( self::$constants === array() ) {
self::$constants = get_defined_constants( true );
}

if ( self::$jsonErrors === array() ) {
foreach ( self::$constants["json"] as $name => $value ) {
if ( !strncmp( $name, "JSON_ERROR_", 11 ) ) {
self::$jsonErrors[$value] = $name;
}
}
}

return isset( self::$jsonErrors[$errorCode] ) ? self::$jsonErrors[$errorCode] : 'UNKNOWN';
}

/**
* @since 2.5
*
* @param integer $errorCode
*
* @return string
*/
public static function getMessageFromJsonErrorCode( $errorCode ) {

$errorMessages = array(
JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch, malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded'
);

if ( !isset( $errorMessages[$errorCode] ) ) {
return self::getStringFromJsonErrorCode( $errorCode );
}

return sprintf(
"Expected a JSON compatible format but failed with '%s'",
$errorMessages[$errorCode]
);
}

}
108 changes: 85 additions & 23 deletions src/ParserParameterProcessor.php
Expand Up @@ -2,6 +2,8 @@

namespace SMW;

use SMW\Libs\ErrorCode;

/**
* @license GNU GPL v2+
* @since 1.9
Expand Down Expand Up @@ -108,18 +110,32 @@ public function toArray() {
/**
* @since 2.3
*
* @param string $key
*
* @return boolean
*/
public function hasParameter( $key ) {
return isset( $this->parameters[$key] ) || array_key_exists( $key, $this->parameters );
}

/**
* @deprecated since 2.5, use ParserParameterProcessor::getParameterValuesByKey
* @since 2.3
*
* @return array
*/
public function getParameterValuesFor( $key ) {
return $this->getParameterValuesByKey( $key );
}

/**
* @since 2.5
*
* @param string $key
*
* @return array
*/
public function getParameterValuesByKey( $key ) {

if ( $this->hasParameter( $key ) ) {
return $this->parameters[$key];
Expand All @@ -129,11 +145,9 @@ public function getParameterValuesFor( $key ) {
}

/**
* Inject parameters from an outside source
*
* @since 1.9
*
* @param array
* @param array $parameters
*/
public function setParameters( array $parameters ) {
$this->parameters = $parameters;
Expand Down Expand Up @@ -172,7 +186,7 @@ private function doMap( array $params ) {
$previousProperty = null;

while ( key( $params ) !== null ) {
$separator = '';

$pipe = false;
$values = array();

Expand All @@ -185,23 +199,7 @@ private function doMap( array $params ) {
$currentElement = explode( '=', trim( current ( $params ) ), 2 );

// Looking to the next element for comparison
if( next( $params ) ) {
$nextElement = explode( '=', trim( current( $params ) ), 2 );

if ( $nextElement !== array() ) {
// This allows assignments of type |Has property=Test1,Test2|+sep=,
// as a means to support multiple value declaration
if ( substr( $nextElement[0], - 5 ) === '+sep' ) {
$separator = isset( $nextElement[1] ) ? $nextElement[1] !== '' ? $nextElement[1] : $this->defaultSeparator : $this->defaultSeparator;
next( $params );
}
}

if ( current( $params ) === '+pipe' ) {
$pipe = true;
next( $params );
}
}
$separator = $this->lookAheadOnNextElement( $params, $pipe );

// First named parameter
if ( count( $currentElement ) == 1 && $previousProperty === null ) {
Expand All @@ -226,7 +224,7 @@ private function doMap( array $params ) {

// Remap properties and values to output a simple array
foreach ( $values as $value ) {
if ( $value !== '' ){
if ( $value !== '' ) {
$results[$currentElement[0]][] = $value;
}
}
Expand All @@ -238,6 +236,70 @@ private function doMap( array $params ) {
}
}

return $results;
return $this->parseFromJson( $results );
}

private function lookAheadOnNextElement( &$params, &$pipe ) {

$separator = '';

if( !next( $params ) ) {
return $separator;
}

$nextElement = explode( '=', trim( current( $params ) ), 2 );

if ( $nextElement !== array() ) {
// This allows assignments of type |Has property=Test1,Test2|+sep=,
// as a means to support multiple value declaration
if ( substr( $nextElement[0], - 5 ) === '+sep' ) {
$separator = isset( $nextElement[1] ) ? $nextElement[1] !== '' ? $nextElement[1] : $this->defaultSeparator : $this->defaultSeparator;
next( $params );
}
}

if ( current( $params ) === '+pipe' ) {
$pipe = true;
next( $params );
}

return $separator;
}

private function parseFromJson( $results ) {

if ( !isset( $results['@json'] ) || !isset( $results['@json'][0] ) ) {
return $results;
}

// Restrict the depth to avoid resolving recursive assignment
// that can not be handled beyond the 2:n
$depth = 3;
$params = json_decode( $results['@json'][0], true, $depth );

if ( $params === null || json_last_error() !== JSON_ERROR_NONE ) {
$this->addError( Message::encode(
array(
'smw-parser-invalid-json-format',
ErrorCode::getStringFromJsonErrorCode( json_last_error() )
)
) );
return $results;
}

array_walk( $params, function( &$value, $key ) {

if ( $value === '' ) {
$value = array();
}

if ( !is_array( $value ) ) {
$value = array( $value );
}
} );

unset( $results['@json'] );
return array_merge( $results, $params );
}

}
20 changes: 20 additions & 0 deletions tests/phpunit/Integration/ByJsonScript/Contents/p-0211.1.json
@@ -0,0 +1,20 @@
@source: http://www.simile-widgets.org/exhibit3/examples/other-versions/HEAD/icd/icd10-infectious.json

{{#subobject:
|@category=P0211
|@json={
"kind": "chapter",
"code": "I",
"exclusion": "carrier or suspected carrier of infectious diseaseZ22.-",
"icdId": "http://id.who.int/icd/icd10/I",
"inclusion": "diseases generally recognized as communicable or transmissible",
"label": "Certain infectious and parasitic diseases",
"link": "http://apps.who.int/classifications/icd10/browse/2010/en#/I",
"subclasses": [
"http://id.who.int/icd/icd10/A00-A09",
"http://id.who.int/icd/icd10/A15-A19",
"http://id.who.int/icd/icd10/A20-A28"
],
"id": "http://id.who.int/icd/icd10/I"
}
}}
27 changes: 27 additions & 0 deletions tests/phpunit/Integration/ByJsonScript/Contents/p-0211.2.json
@@ -0,0 +1,27 @@
@source: http://www.simile-widgets.org/exhibit3/examples/other-versions/HEAD/icd/icd10-infectious.json

{{#set:
|@json={
"kind": "block",
"code": "A00-A09",
"icdId": "http://id.who.int/icd/icd10/A00-A09",
"label": "Intestinal infectious diseases",
"link": "http://apps.who.int/classifications/icd10/browse/2010/en#/A00-A09",
"superclasses": [
"http://id.who.int/icd/icd10/I"
],
"subclasses": [
"http://id.who.int/icd/icd10/A00",
"http://id.who.int/icd/icd10/A01",
"http://id.who.int/icd/icd10/A02",
"http://id.who.int/icd/icd10/A03",
"http://id.who.int/icd/icd10/A04",
"http://id.who.int/icd/icd10/A05",
"http://id.who.int/icd/icd10/A06",
"http://id.who.int/icd/icd10/A07",
"http://id.who.int/icd/icd10/A08",
"http://id.who.int/icd/icd10/A09"
],
"id": "http://id.who.int/icd/icd10/A00-A09"
}
}}
11 changes: 11 additions & 0 deletions tests/phpunit/Integration/ByJsonScript/Contents/p-0211.3.json
@@ -0,0 +1,11 @@
3+ depth is not permitted

{{#set:
|@json={
"foo": {
"kind": {
"kind": "bar"
}
}
}
}}