Daniel Werner has submitted this change and it was merged.

Change subject: (Bug 42063, bug 44577) RDF for Special:EntityData
......................................................................


(Bug 42063, bug 44577) RDF for Special:EntityData

Special:EntityData supports RDF/XML output when using the .rdf postfix and 
N-Triples output when using the .nt postfix.
EasyRdf ( http://www.easyrdf.org ) is introduced for easy modeling and 
exporting of the data model as RDF.

Patch by Anja Jentzsch, Thomas Pellissier Tanon, Daniel Kinzler

Change-Id: Ie9f2f8944a65750077152a69923c0a0b18a49d5e
---
A .gitmodules
M lib/includes/specials/SpecialWikibasePage.php
M repo/Wikibase.classes.php
M repo/Wikibase.php
M repo/includes/WikibaseRepo.php
A repo/includes/content/RdfSerializer.php
A repo/includes/content/easyRdf
M repo/includes/specials/SpecialEntityData.php
M repo/tests/phpunit/includes/WikibaseRepoTest.php
A repo/tests/phpunit/includes/content/RdfSerializerTest.php
M repo/tests/phpunit/includes/specials/SpecialEntityDataTest.php
11 files changed, 870 insertions(+), 48 deletions(-)

Approvals:
  Daniel Werner: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/.gitmodules b/.gitmodules
new file mode 100755
index 0000000..794faa1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "repo/includes/content/easyRdf"]
+       path = repo/includes/content/easyRdf
+       url = https://github.com/Wikidata/easyrdf_lite.git
diff --git a/lib/includes/specials/SpecialWikibasePage.php 
b/lib/includes/specials/SpecialWikibasePage.php
index e254315..74d4815 100644
--- a/lib/includes/specials/SpecialWikibasePage.php
+++ b/lib/includes/specials/SpecialWikibasePage.php
@@ -67,8 +67,6 @@
         * @since 0.1
         *
         * @param string|null $subPage
-        *
-        * @return boolean
         */
        public function execute( $subPage ) {
                $subPage = is_null( $subPage ) ? '' : $subPage;
@@ -81,10 +79,7 @@
                // If the user is authorized, display the page, if not, show an 
error.
                if ( !$this->userCanExecute( $this->getUser() ) ) {
                        $this->displayRestrictionError();
-                       return false;
                }
-
-               return true;
        }
 
 }
diff --git a/repo/Wikibase.classes.php b/repo/Wikibase.classes.php
index 20394f4..3b8136c 100644
--- a/repo/Wikibase.classes.php
+++ b/repo/Wikibase.classes.php
@@ -96,6 +96,30 @@
                'Wikibase\ItemHandler' => 'includes/content/ItemHandler.php',
                'Wikibase\PropertyContent' => 
'includes/content/PropertyContent.php',
                'Wikibase\PropertyHandler' => 
'includes/content/PropertyHandler.php',
+               'Wikibase\RdfSerializer' => 
'includes/content/RdfSerializer.php',
+
+               // EasyRdf
+               'EasyRdf_Exception' => 
'includes/content/easyRdf/EasyRdf/Exception.php',
+               'EasyRdf_Format' => 
'includes/content/easyRdf/EasyRdf/Format.php',
+               'EasyRdf_Graph' => 'includes/content/easyRdf/EasyRdf/Graph.php',
+               'EasyRdf_Namespace' => 
'includes/content/easyRdf/EasyRdf/Namespace.php',
+               'EasyRdf_Literal' => 
'includes/content/easyRdf/EasyRdf/Literal.php',
+               'EasyRdf_Literal_Boolean' => 
'includes/content/easyRdf/EasyRdf/Literal/Boolean.php',
+               'EasyRdf_Literal_Date' => 
'includes/content/easyRdf/EasyRdf/Literal/Date.php',
+               'EasyRdf_Literal_DateTime' => 
'includes/content/easyRdf/EasyRdf/Literal/DateTime.php',
+               'EasyRdf_Literal_Decimal' => 
'includes/content/easyRdf/EasyRdf/Literal/Decimal.php',
+               'EasyRdf_Literal_HexBinary' => 
'includes/content/easyRdf/EasyRdf/Literal/HexBinary.php',
+               'EasyRdf_Literal_Integer' => 
'includes/content/easyRdf/EasyRdf/Literal/Integer.php',
+               'EasyRdf_Resource' => 
'includes/content/easyRdf/EasyRdf/Resource.php',
+               'EasyRdf_Serialiser' => 
'includes/content/easyRdf/EasyRdf/Serialiser.php',
+               'EasyRdf_Serialiser_GraphViz' => 
'includes/content/easyRdf/EasyRdf/Serialiser/GraphViz.php',
+               'EasyRdf_Serialiser_RdfPhp' => 
'includes/content/easyRdf/EasyRdf/Serialiser/RdfPhp.php',
+               'EasyRdf_Serialiser_Ntriples' => 
'includes/content/easyRdf/EasyRdf/Serialiser/Ntriples.php',
+               'EasyRdf_Serialiser_Json' => 
'includes/content/easyRdf/EasyRdf/Serialiser/Json.php',
+               'EasyRdf_Serialiser_RdfXml' => 
'includes/content/easyRdf/EasyRdf/Serialiser/RdfXml.php',
+               'EasyRdf_Serialiser_Turtle' => 
'includes/content/easyRdf/EasyRdf/Serialiser/Turtle.php',
+               'EasyRdf_TypeMapper' => 
'includes/content/easyRdf/EasyRdf/TypeMapper.php',
+               'EasyRdf_Utils' => 'includes/content/easyRdf/EasyRdf/Utils.php',
 
                // includes/specials
                'SpecialNewEntity' => 'includes/specials/SpecialNewEntity.php',
diff --git a/repo/Wikibase.php b/repo/Wikibase.php
old mode 100644
new mode 100755
diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php
index e3245b6..da21dc7 100644
--- a/repo/includes/WikibaseRepo.php
+++ b/repo/includes/WikibaseRepo.php
@@ -76,6 +76,19 @@
        }
 
        /**
+        * Returns the base to use when generating URIs for use in RDF output.
+        *
+        * @return string
+        */
+       public function getRdfBaseURI() {
+               global $wgServer; //TODO: make this configurable
+
+               $uri = $wgServer;
+               $uri = preg_replace( '!^//!', 'http://', $uri );
+               return $uri;
+       }
+
+       /**
         * @since 0.4
         *
         * @return EntityIdParser
diff --git a/repo/includes/content/RdfSerializer.php 
b/repo/includes/content/RdfSerializer.php
new file mode 100755
index 0000000..01c3e36
--- /dev/null
+++ b/repo/includes/content/RdfSerializer.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace Wikibase;
+
+/**
+ * RDF mapping for wikibase data model.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.4
+ *
+ * @file
+ * @ingroup WikibaseRepo
+ * @ingroup Content
+ *
+ * @licence GNU GPL v2+
+ * @author Anja Jentzsch < [email protected] >
+ * @author Thomas Pellissier Tanon
+ * @author Daniel Kinzler
+ */
+
+use DataTypes\DataTypeFactory;
+use EasyRdf_Exception;
+use EasyRdf_Format;
+use EasyRdf_Graph;
+use EasyRdf_Namespace;
+
+class RdfSerializer {
+
+       const ontologyBaseUri = 'http://www.wikidata.org'; //XXX: Denny made me 
put the "www" there...
+
+       /**
+        * @var DataTypeFactory
+        */
+       protected $dataTypeFactory;
+
+       /**
+        * @var EntityLookup
+        */
+       protected $entityLookup;
+
+       /**
+        * @var EasyRdf_Format
+        */
+       protected $format;
+
+       /**
+        * Map of gnames to namespace URIs
+        *
+        * @var array
+        */
+       protected $namespaces = array();
+
+       /**
+        * @param EasyRdf_Format  $format
+        * @param string          $uriBase
+        * @param EntityLookup    $entityLookup
+        * @param DataTypeFactory $dataTypeFactory
+        */
+       public function __construct(
+               EasyRdf_Format $format,
+               $uriBase,
+               EntityLookup $entityLookup,
+               DataTypeFactory $dataTypeFactory
+       ) {
+               $this->uriBase = $uriBase;
+               $this->format = $format;
+               $this->entityLookup = $entityLookup;
+               $this->dataTypeFactory = $dataTypeFactory;
+
+               $this->namespaces = array(
+                       'wikibase_ontology' => self::ontologyBaseUri . 
'/ontology/',
+                       'wikibase_entity' => $this->uriBase . '/entity/',
+                       'wikibase_data' => $this->uriBase . '/data/',
+                       'wikibase_property' => $this->uriBase . '/property/',
+                       'wikibase_value' => $this->uriBase . '/value/',
+                       'wikibase_qualifier' => $this->uriBase . '/qualifier/',
+                       'wikibase_statement' => $this->uriBase . '/statement/',
+               );
+       }
+
+       /**
+        * Checks whether the necessary libraries for RDF serialization are 
installed.
+        */
+       public static function isSupported() {
+               // check that the submodule is present
+               $file = __DIR__ . '/easyRdf/EasyRdf.php';
+               return file_exists( $file ) && class_exists( 'EasyRdf_Graph' );
+       }
+
+       /**
+        * Returns an EasyRdf_Format object for the given format name.
+        * The name may be a MIME type or a file extension (or a format URI
+        * or canonical name).
+        *
+        * If no format is found for $name, or EasyRdf is not installed,
+        * this method returns null.
+        *
+        * @param $name the name (file extension, mime type) of the desired 
format.
+        *
+        * @return EasyRdf_Format|null the format object, or null if not found.
+        */
+       public static function getFormat( $name ) {
+               if ( !self::isSupported() ) {
+                       wfWarn( "EasyRdf not found" );
+                       return null;
+               }
+
+               try {
+                       $format = EasyRdf_Format::getFormat( $name );
+                       return $format;
+               } catch ( EasyRdf_Exception $ex ) {
+                       // noop
+               }
+
+               return null;
+       }
+
+       public function getNamespaces() {
+               return $this->namespaces;
+       }
+
+       /**
+        * Inits a new Graph
+        *
+        * @return EasyRdf_Graph
+        */
+       protected function newRdfGraph() {
+               //register namespaces (ugh, static crap)
+
+               foreach ( $this->getNamespaces() as $gname => $uri ) {
+                       EasyRdf_Namespace::set( $gname, $uri );
+               }
+
+               return new EasyRdf_Graph();
+       }
+
+       /**
+        * Add an entity to the RDF graph
+        *
+        * @param Entity $entity the entity to output.
+        * @param \Revision $revision for meta data (optional)
+        *
+        * @return EasyRdf_Graph
+        */
+       public function buildGraphForEntity( Entity $entity, \Revision 
$revision = null ) {
+               $graph = $this->newRdfGraph();
+
+               $entityUri = 'wikibase_id:' . ucfirst( 
$entity->getId()->getPrefixedId() );
+               $entityResource = $graph->resource( $entityUri );
+
+               //TODO: filter language
+
+               foreach ( $entity->getLabels() as $languageCode => $labelText ) 
{
+                       //TODO: also skos:prefLabel
+                       $entityResource->addLiteral( 'rdfs:label', $labelText, 
$languageCode );
+               }
+
+               foreach ( $entity->getDescriptions() as $languageCode => 
$description ) {
+                       //TODO: use skos:note
+                       $entityResource->addLiteral( 'rdfs:comment', 
$description, $languageCode );
+               }
+
+               foreach ( $entity->getAllAliases() as $languageCode => $aliases 
) {
+                       foreach ( $aliases as $alias ) {
+                               //TODO: use skos:altLabel
+                               $entityResource->addLiteral( 
'wikibase_ontology:knownAs', $alias, $languageCode );
+                       }
+               }
+
+               $claims = $entity->getClaims();
+               $claimsByProperty = array();
+               foreach( $claims as $claim ) {
+                       $propertyId = $claim->getMainSnak()->getPropertyId();
+                       $claimsByProperty[$propertyId->getNumericId()][] = 
$claim;
+               }
+
+               //TODO: (optionally?) find referenced entities (items and 
properties)
+               //      and include basic info about them
+
+               /* @var Claim[] $claims */
+               foreach( $claimsByProperty as $claims ) {
+                       $counter = 1;
+                       $propertyId = 
$claims[0]->getMainSnak()->getPropertyId();
+                       $propertyUri = 'wikibase_property:' . strval( 
$propertyId );
+
+                       /* @var Property $property */
+                       $property = $this->entityLookup->getEntity( $propertyId 
);
+
+                       //$property->getEntity()->getLabel( $languageCode );
+                       $valueUri = 'wikibase_value:' . strval( $propertyId );
+
+                       foreach( $claims as $claim ) {
+                               $snak = $claim->getMainSnak();
+                               if ( $snak instanceof PropertyValueSnak ) {
+                                       $value = 
$claim->getMainSnak()->getDataValue()->getValue();
+
+                                       $statementUri = 'wikibase_statement:' . 
ucfirst( $claim->getGuid() );
+                                       $statementResource = $graph->resource( 
$statementUri, array( 'wikibase_ontology:Statement' ) );
+
+                                       $entityResource->addResource( 
$propertyUri, $statementResource );
+                                       if ( $property->getType() == 
$this->dataTypeFactory->getType( 'wikibase-item' ) ) {
+                                               $value = 'wikibase_id:' . 
ucfirst( $value );
+                                               
$statementResource->addResource( $valueUri, $value );
+                                       }
+                                       if ( $property->getType() == 
$this->dataTypeFactory->getType( 'commonsMedia' ) ) {
+                                               
$statementResource->addResource( $valueUri, $value );
+                                       }
+
+                                       //TODO: sane mechanism for extending 
this? Some kind of expert logic?
+
+                                       $counter += 1;
+                               }
+
+                               //TODO: handle NoValueSnak and SomeValueSnak!
+                       }
+               }
+
+               // TODO: sitelinks, use skos:isPrimarySubjectOf or 
foaf:primaryTopic?
+               // + state language of resource
+               // TODO: use rdf:about (or what?!) to link subject URI to 
document URI
+
+               return $graph;
+       }
+
+       /**
+        * Returns the serialized graph
+        *
+        * @param EasyRdf_Graph $graph the graph to serialize
+        *
+        * @return string
+        */
+       public function serializeRdf( EasyRdf_Graph $graph ) {
+               $serialiser = $this->format->newSerialiser();
+               $data = $serialiser->serialise( $graph, 
$this->format->getName() );
+
+               assert( is_string( $data ) );
+               return $data;
+       }
+
+       /**
+        * Returns the serialized entity.
+        * Shorthand for $this->serializeRdf( $this->buildGraphForEntity( 
$entity ) ).
+        *
+        * @param Entity   $entity   the entity to serialize
+        * @param \Revision $revision for meta data (optional)
+        *
+        * @return string
+        */
+       public function serializeEntity( Entity $entity, \Revision $revision = 
null ) {
+               $graph = $this->buildGraphForEntity( $entity, $revision );
+               $data = $this->serializeRdf( $graph );
+               return $data;
+       }
+
+       public function getDefaultMimeType() {
+               return $this->format->getDefaultMimeType();
+       }
+}
diff --git a/repo/includes/content/easyRdf b/repo/includes/content/easyRdf
new file mode 160000
index 0000000..f74a880
--- /dev/null
+++ b/repo/includes/content/easyRdf
+Subproject commit f74a88084d3a0f8e15a6e5d5a311e8d4876cc7c5
diff --git a/repo/includes/specials/SpecialEntityData.php 
b/repo/includes/specials/SpecialEntityData.php
old mode 100644
new mode 100755
index d5a3b84..7b35bc1
--- a/repo/includes/specials/SpecialEntityData.php
+++ b/repo/includes/specials/SpecialEntityData.php
@@ -1,10 +1,11 @@
 <?php
 
+use DataTypes\DataTypeFactory;
+use \Wikibase\Entity;
 use \Wikibase\EntityContent;
 use \Wikibase\EntityContentFactory;
 use \Wikibase\EntityId;
-
-//XXX: our special pages are not in the wikibase namespace. why? some crappy 
internal magic going on?
+use \Wikibase\RdfSerializer;
 
 /**
  * Special page to act as a data endpoint for the linked data web.
@@ -18,13 +19,15 @@
  * representation of data entities. Using the ContentHandler to serialize the 
entity would expose
  * internal implementation details.
  *
- * @since 0.3
+ * @since 0.4
  *
  * @file 
  * @ingroup WikibaseRepo
  *
  * @licence GNU GPL v2+
  * @author Daniel Kinzler
+ * @author Thomas Pellissier Tanon
+ * @author Anja Jentzsch < [email protected] >
  */
 class SpecialEntityData extends SpecialWikibasePage {
 
@@ -45,9 +48,24 @@
        );
 
        /**
+        * @var string
+        */
+       protected $rdfBaseURI = null;
+
+       /**
+        * @var Wikibase\EntityLookup
+        */
+       protected $entityLookup = null;
+
+       /**
+        * @var DataTypeFactory
+        */
+       protected $dataTypeFactory = null;
+
+       /**
         * Constructor.
         *
-        * @since 0.1
+        * @since 0.4
         */
        public function __construct() {
                parent::__construct( 'EntityData' );
@@ -56,11 +74,9 @@
        /**
         * Main method.
         *
-        * @since 0.1
+        * @since 0.4
         *
         * @param string|null $subPage
-        *
-        * @return boolean
         */
        public function execute( $subPage ) {
                $revision = 0;
@@ -92,13 +108,35 @@
                        return;
                }
 
+               $repo = \Wikibase\Repo\WikibaseRepo::getDefaultInstance();
+               $this->rdfBaseURI = $repo->getRdfBaseURI();
+               $this->entityLookup = 
\Wikibase\StoreFactory::getStore()->getEntityLookup();
+               $this->dataTypeFactory = $repo->getDataTypeFactory();
+
+
+               $this->showData( $format, $id, $revision );
+       }
+
+       /**
+        * Output entity data.
+        *
+        * @param string $format The name (mime type of file extension) of the 
format to use
+        * @param string $id The entity ID
+        * @param int|string $revision The entity revision
+        *
+        * @throws HttpError
+        */
+       public function showData( $format, $id, $revision ) {
+
                //TODO: handle IfModifiedSince!
 
+               $serializer = $this->createApiSerializer( $format );
 
-               $apiFormat = self::getApiFormatName( $format );
-               $printer = $apiFormat === null ? null : 
$this->createApiPrinter( $apiFormat );
+               if ( !$serializer ) {
+                       $serializer = $this->createRdfSerializer( $format );
+               }
 
-               if ( !$printer ) {
+               if ( !$serializer ) {
                        throw new \HttpError( 415, wfMessage( 
'wikibase-entitydata-unsupported-format' )->params( $format ) );
                }
 
@@ -140,7 +178,16 @@
                        $rev = $page->getRevision();
                }
 
-               $this->showData( $entity, $printer, $rev );
+
+               if( $serializer instanceof \Wikibase\RdfSerializer ) {
+                       $data = $serializer->serializeEntity( 
$entity->getEntity(), $rev );
+                       $contentType = $serializer->getDefaultMimeType();
+               } else {
+                       $data = $this->apiSerialize( $entity->getEntity(), 
$serializer, $rev );
+                       $contentType = $serializer->getIsHtml() ? 'text/html' : 
$serializer->getMimeType();
+               }
+
+               $this->outputData( $data, $contentType, $rev );
        }
 
        /**
@@ -203,24 +250,51 @@
        }
 
        /**
-        * Creates a printer that can generate the given output format.
+        * Creates an API printer that can generate the given output format.
         *
         * @param String $format The desired serialization format,
-        *   as a format name understood by ApiBase.
+        *   as a format name understood by ApiBase or EasyRdf_Format
         *
         * @return ApiFormatBase|null A suitable result printer, or null
+        *         if the given format is not supported by the API.
+        */
+       protected function createApiSerializer( $format ) {
+               //MediaWiki formats
+               $api = $this->getApiMain( $format );
+               $formats = $api->getFormats();
+               $formatName = self::getApiFormatName( $format );
+               if ( $formatName !== null && array_key_exists( $formatName, 
$formats ) ) {
+                       return $api->createPrinterByName( $formatName );
+               }
+
+               return null;
+       }
+
+       /**
+        * Creates an Rdf Serializer that can generate the given output format.
+        *
+        * @param String $format The desired serialization format,
+        *   as a format name understood by ApiBase or EasyRdf_Format
+        *
+        * @return RdfSerializer|null A suitable result printer, or null
         *   if the given format is not supported.
         */
-       protected function createApiPrinter( $format ) {
-               $api = $this->getApiMain( $format );
+       protected function createRdfSerializer( $format ) {
+               //MediaWiki formats
+               $rdfFormat = \Wikibase\RdfSerializer::getFormat( $format );
 
-               $formats = $api->getFormats();
-               if ( !array_key_exists( $format, $formats ) ) {
+               if ( !$rdfFormat ) {
                        return null;
                }
 
-               $printer = $api->createPrinterByName( $format );
-               return $printer;
+               $serializer = new RdfSerializer(
+                       $rdfFormat,
+                       $this->rdfBaseURI,
+                       $this->entityLookup,
+                       $this->dataTypeFactory
+               );
+
+               return $serializer;
        }
 
        /**
@@ -229,19 +303,19 @@
         * result, if $printer was generated from that same ApiMain module, as
         * createApiPrinter() does.
         *
-        * @param Wikibase\EntityContent $entityContent The entity to convert 
ot an ApiResult
+        * @param Wikibase\Entity $entity The entity to convert ot an ApiResult
         * @param ApiFormatBase $printer The output printer that will be used 
for serialization.
         *   Used to provide context for generating the ApiResult, and may also 
be manipulated
         *   to fine-tune the output.
         *
         * @return ApiResult
         */
-       protected function generateApiResult( EntityContent $entityContent, 
ApiFormatBase $printer ) {
+       protected function generateApiResult( Entity $entity, ApiFormatBase 
$printer ) {
                wfProfileIn( __METHOD__ );
 
                $format = strtolower( $printer->getFormat() ); //XXX: hack!
 
-               $entityKey = 'entity'; //XXX: perhaps better: 
$entity->getEntity()->getType();
+               $entityKey = 'entity'; //XXX: perhaps better: 
$entity->getType();
                $basePath = array();
 
                $res = $this->getApiMain( $format )->getResult();
@@ -260,14 +334,14 @@
                }
 
                $serializerFactory = new 
\Wikibase\Lib\Serializers\SerializerFactory();
-               $serializer =$serializerFactory->newSerializerForObject( 
$entityContent->getEntity() );
+               $serializer =$serializerFactory->newSerializerForObject( 
$entity );
 
                $opt = new 
\Wikibase\Lib\Serializers\EntitySerializationOptions();
                $opt->setIndexTags( $res->getIsRawMode() ); //FIXME: 
$res->rawMode doesn't seem to be set to what we want.
                $opt->setProps( self::$fieldsToShow );      //FIXME: someone 
does not know how to write clear FIXMEs
                $serializer->setOptions( $opt );
 
-               $arr = $serializer->getSerialized( $entityContent->getEntity() 
);
+               $arr = $serializer->getSerialized( $entity );
 
                // we want the entity to *be* the result, not *in* the result
                foreach ( $arr as $key => $value ) {
@@ -279,17 +353,19 @@
        }
 
        /**
-        * Serialize the entity data using the provided format and send it as 
the HTTP response body.
+        * Serialize the entity data using the provided format.
         *
         * Note that we are using the API's serialization facility to ensure a 
consistent external
         * representation of data entities. Using the ContentHandler to 
serialize the entity would
         * expose internal implementation details.
         *
-        * @param Wikibase\EntityContent $entity the entity to output.
+        * @param Wikibase\Entity $entity the entity to output.
         * @param ApiFormatBase $printer the printer to use to generate the 
output
         * @param Revision $revision the entity's revision (optional)
+        *
+        * @return string the serialized data
         */
-       public function output( EntityContent $entity, ApiFormatBase $printer, 
Revision $revision ) {
+       public function apiSerialize( Entity $entity, ApiFormatBase $printer, 
Revision $revision = null ) {
                // NOTE: The way the ApiResult is provided to $printer is 
somewhat
                //       counter-intuitive. Basically, the relevant ApiResult 
object
                //       is owned by the ApiMain module provided by 
getApiMain().
@@ -300,34 +376,37 @@
 
                //XXX: really inject meta-info? where else should we put it?
                $basePath = array();
-               $res->addValue( $basePath , '_revision_', intval( 
$revision->getId() ) );
-               $res->addValue( $basePath , '_modified_', wfTimestamp( 
TS_ISO_8601, $revision->getTimestamp() ) );
 
+               if ( $revision ) {
+                       $res->addValue( $basePath , '_revision_', intval( 
$revision->getId() ) );
+                       $res->addValue( $basePath , '_modified_', wfTimestamp( 
TS_ISO_8601, $revision->getTimestamp() ) );
+               }
 
                $this->getOutput()->disable(); // don't generate HTML
 
                $printer->profileIn();
                $printer->initPrinter( false );
+               $printer->setBufferResult( true );
 
                // Outputs the ApiResult held by the ApiMain module, which is 
hopefully the same as $res
+               //NOTE: this can and will mess with the HTTP response!
                $printer->execute();
+               $data = $printer->getBuffer();
 
                $printer->closePrinter();
                $printer->profileOut();
 
-               //FIXME: Content-Length is bogus! Where does it come from?!
+               return $data;
        }
 
        /**
         * Output the entity data and set the appropriate HTTP response headers.
         *
-        * @param Wikibase\EntityContent $entity the entity to output.
-        * @param ApiFormatBase $printer the printer to use to generate the 
output
-        * @param Revision $revision the entity's revision (optional)
-        *
-        * @throws HttpError If an unsupported format is requested.
+        * @param string $data the data to output
+        * @param string $contentType the data's mime type
+        * @param Revision $revision
         */
-       public function showData( EntityContent $entity, ApiFormatBase 
$printer, Revision $revision = null ) {
+       public function outputData( $data, $contentType, Revision $revision = 
null ) {
                global $wgSquidMaxage;
 
                // NOTE: similar code as in RawAction::onView, keep in sync.
@@ -342,18 +421,30 @@
                $maxage  = max( 0, min( 60 * 60 * 24 * 30, $maxage ) );
                $smaxage = max( 0, min( 60 * 60 * 24 * 30, $smaxage ) );
 
-               //TODO: set Last-Modified header? Why doesn't mediawiki set 
that for article pages?
-
-               // make sure we are reporting the correct content type
-               $contentType = $printer->getIsHtml() ? 'text/html' : 
$printer->getMimeType();
                $response->header( 'Content-Type: ' . $contentType . '; 
charset=UTF-8' );
+               $response->header( 'Content-Length: ' . strlen( $data ) );
+
+               if ( $revision ) {
+                       $response->header( 'Last-Modified: ' . wfTimestamp( 
TS_ISO_8601, $revision->getTimestamp() ) );
+               }
+
+               //Set X-Frame-Options API results (bug 39180)
+               global $wgApiFrameOptions;
+               if ( $wgApiFrameOptions ) {
+                       $response->header( "X-Frame-Options: 
$wgApiFrameOptions" );
+               }
 
                // allow the client to cache this
                $mode = 'public';
                $response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . 
$smaxage . ', max-age=' . $maxage );
-               //FIXME: Cache-Control and Expires headers are apparently 
overwritten later!
 
-               $this->output( $entity, $printer, $revision );
+               $this->getOutput()->disable(); // don't generate HTML
+               ob_clean();
+
+               print $data;
+               flush();
+
+               //die(); //FIXME: figure out how to best shut down here.
        }
 
 }
diff --git a/repo/tests/phpunit/includes/WikibaseRepoTest.php 
b/repo/tests/phpunit/includes/WikibaseRepoTest.php
index a4ad7c9..f8171c9 100644
--- a/repo/tests/phpunit/includes/WikibaseRepoTest.php
+++ b/repo/tests/phpunit/includes/WikibaseRepoTest.php
@@ -34,8 +34,9 @@
  *
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
+ * @author Daniel Kinzler
  */
-class WikibaseRepoTest extends \PHPUnit_Framework_TestCase {
+class WikibaseRepoTest extends \MediaWikiTestCase {
 
        /**
         * @return WikibaseRepo
@@ -59,4 +60,21 @@
                $this->assertInstanceOf( 'Wikibase\Lib\EntityIdParser', 
$returnValue );
        }
 
+       public static function provideGetRdfBaseURI() {
+               return array(
+                       array ( 'http://acme.test', 'http://acme.test' ),
+                       array ( 'https://acme.test', 'https://acme.test' ),
+                       array ( '//acme.test', 'http://acme.test' ),
+               );
+       }
+
+       /**
+        * @dataProvider provideGetRdfBaseURI
+        */
+       public function testGetRdfBaseURI( $server, $expected ) {
+               $this->setMwGlobals( 'wgServer', $server );
+
+               $returnValue = $this->newInstance()->getRdfBaseURI();
+               $this->assertEquals( $expected, $returnValue );
+       }
 }
diff --git a/repo/tests/phpunit/includes/content/RdfSerializerTest.php 
b/repo/tests/phpunit/includes/content/RdfSerializerTest.php
new file mode 100644
index 0000000..7de00d5
--- /dev/null
+++ b/repo/tests/phpunit/includes/content/RdfSerializerTest.php
@@ -0,0 +1,384 @@
+<?php
+
+namespace Wikibase\Test;
+use DataTypes\DataTypeFactory;
+use EasyRdf_Namespace;
+use MediaWikiSite;
+use Wikibase\Entity;
+use Wikibase\EntityId;
+use Wikibase\Item;
+use Wikibase\RdfSerializer;
+use Wikibase\SiteLink;
+
+/**
+ * Tests for the Wikibase\RdfSerializer class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 0.4
+ *
+ * @ingroup WikibaseRepoTest
+ * @ingroup Test
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseProperty
+ * @group WikibaseEntity
+ * @group WikibaseEntityHandler
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class RdfSerializerTest extends \MediaWikiTestCase {
+
+       protected static $formats = array(
+               'rdf',
+               'application/rdf+xml',
+               'n3',
+               'text/n3',
+               'nt',
+               'ntriples',
+               'turtle',
+       );
+
+       protected static $dataTypes = array(
+               'commonsMedia' => array(
+                       'datavalue' => 'string',
+               ),
+               'string' => array(
+                       'datavalue' => 'string',
+               ),
+               'geo-coordinate' => array(
+                       'datavalue' => 'geocoordinate',
+               ),
+               'quantity' => array(
+                       'datavalue' => 'quantity',
+               ),
+               'monolingual-text' => array(
+                       'datavalue' => 'monolingualtext',
+               ),
+               'multilingual-text' => array(
+                       'datavalue' => 'multilingualtext',
+               ),
+               'time' => array(
+                       'datavalue' => 'time',
+               ),
+       );
+
+       protected static $uriBase = 'http://acme.test';
+
+       public function setUp() {
+               parent::setUp();
+
+               if ( !RdfSerializer::isSupported() ) {
+                       $this->markTestSkipped( "RDF library not found" );
+               }
+       }
+
+       /**
+        * @return Entity[]
+        */
+       protected static function getTestEntities() {
+               static $entities = array();
+
+               if ( !empty( $entities ) ) {
+                       return $entities;
+               }
+
+               $entity = Item::newEmpty();
+               $entities['empty'] = $entity;
+
+
+               $entity = Item::newEmpty();
+               $entities['terms'] = $entity;
+
+               $entity->setLabel( 'en', 'Berlin' );
+               $entity->setLabel( 'ru', 'Берлин' );
+
+               $entity->setDescription( 'en', 'German city' );
+               $entity->setDescription( 'ru', 'столица и одновременно земля 
Германии' );
+
+               $entity->addAliases( 'en', array( 'Berlin, Germany', 'Land 
Berlin' ) );
+               $entity->addAliases( 'ru', array( 'Berlin' ) );
+
+               // not yet supported
+               /*
+               $entity = Item::newEmpty();
+               $entities['links'] = $entity;
+
+               $entity->setLabel( 'en', 'Berlin' );
+
+               $entity->addSiteLink( new SiteLink( 
MediaWikiSite::newFromGlobalId( 'enwiki' ), 'Berlin' ) );
+               $entity->addSiteLink( new SiteLink( 
MediaWikiSite::newFromGlobalId( 'dewiki' ), 'Berlin' ) );
+               $entity->addSiteLink( new SiteLink( 
MediaWikiSite::newFromGlobalId( 'ruwiki' ), 'Берлин' ) );
+               */
+
+               $i = 1;
+               foreach ( $entities as $entity ) {
+                       $entity->setId( new EntityId( Item::ENTITY_TYPE, $i++ ) 
);
+               }
+
+               return $entities;
+       }
+
+       /**
+        * @param \Wikibase\EntityId $entityId
+        * @param array $properties
+        *
+        * @return \EasyRdf_Graph
+        */
+       protected static function makeEntityGraph( EntityId $entityId, 
$properties ) {
+               $graph = new \EasyRdf_Graph();
+
+               $entityUri = 'wikibase_id:' . ucfirst( 
$entityId->getPrefixedId() );
+               $entityResource = $graph->resource( $entityUri );
+
+               /* @var \EasyRdf_Resource $entityResource */
+               foreach ( $properties as $prop => $values ) {
+                       if ( is_scalar( $values ) ) {
+                               $values = array( $values );
+                       }
+
+                       foreach ( $values as $val ) {
+                               $entityResource->add( $prop, $val );
+                       }
+               }
+
+               return $graph;
+       }
+
+       /**
+        * @return \EasyRdf_Graph[]
+        */
+       protected static function getTestGraphs() {
+               static $graphs = array();
+
+               if ( !empty( $graphs ) ) {
+                       return $graphs;
+               }
+
+               $serializer = self::newRdfSerializer( 'rdf' ); // dummy
+
+               foreach ( $serializer->getNamespaces() as $gname => $uri ) {
+                       EasyRdf_Namespace::set( $gname, $uri );
+               }
+
+               $entities = self::getTestEntities();
+
+               $graphs['empty'] = self::makeEntityGraph(
+                       $entities['empty']->getId(),
+                       array()
+               );
+
+               $graphs['terms'] = self::makeEntityGraph(
+                       $entities['terms']->getId(),
+                       array(
+                               'rdfs:label' => array(
+                                       new \EasyRdf_Literal( 'Berlin', 'en' ),
+                                       new \EasyRdf_Literal( 'Берлин', 'ru' )
+                               ),
+                               'rdfs:comment' => array(
+                                       new \EasyRdf_Literal( 'German city', 
'en' ),
+                                       new \EasyRdf_Literal( 'столица и 
одновременно земля Германии', 'ru' )
+                               ),
+                               'wikibase_ontology:knownAs' => array(
+                                       new \EasyRdf_Literal( 'Berlin, 
Germany', 'en' ),
+                                       new \EasyRdf_Literal( 'Land Berlin', 
'en' ),
+                                       new \EasyRdf_Literal( 'Berlin', 'ru' )
+                               ),
+                       )
+               );
+
+               // not yet supportted
+               /*
+               $graphs['links'] = self::makeEntityGraph(
+                       $entities['links']->getId(),
+                       array(
+                               //'rdfs:label', new \EasyRdf_Literal( '', '' )
+                       )
+               );
+               */
+
+               return $graphs;
+       }
+
+       protected static function getTestDataPatterns() {
+               static $patterns = array();
+
+               if ( !empty( $patterns ) ) {
+                       return $patterns;
+               }
+
+               $patterns['empty']['rdf'] = '!<rdf:RDF.*</rdf:RDF>!s';
+               $patterns['empty']['n3']  = '!!s';
+
+               $patterns['terms']['rdf'] = '!<rdf:RDF.*'
+                       . '<rdf:Description.*rdf:about="wikibase_id:Q2".*'
+                       . '<rdfs:label xml:lang="en">Berlin</rdfs:label>.*'
+                       . '<rdfs:comment xml:lang="en">German 
city</rdfs:comment>.*'
+                       . '<wikibase_ontology:knownAs xml:lang="en">Berlin, 
Germany</wikibase_ontology:knownAs>.*'
+                       . '</rdf:RDF>!s';
+               $patterns['terms']['n3']  = '!<wikibase_id:Q2>.*'
+                       . 'rdfs:label +"Berlin"@en,.*'
+                       . 'rdfs:comment +"German city"@en,.*'
+                       . 'wikibase_ontology:knownAs +"Berlin, Germany"@en,.*'
+                       . '!s';
+
+               // links not yet supported
+               /*
+               $patterns['links']['rdf'] = '!xxx!s';
+               $patterns['links']['n3']  = '!xxx!s';
+               */
+
+               return $patterns;
+       }
+
+
+       protected static function newRdfSerializer( $formatName ) {
+               $format = RdfSerializer::getFormat( $formatName );
+
+               $dataTypes = new DataTypeFactory( self::$dataTypes );
+
+               $mockRepo = new MockRepository();
+
+               foreach( self::getTestEntities() as $entity ) {
+                       $mockRepo->putEntity( $entity );
+               }
+
+               return new RdfSerializer(
+                       $format,
+                       self::$uriBase,
+                       $mockRepo,
+                       $dataTypes
+               );
+       }
+
+       public static function provideGetFormat() {
+               return array_map(
+                       function ( $format ) {
+                               return array( $format );
+                       },
+                       self::$formats
+               );
+       }
+
+       /**
+        * @dataProvider provideGetFormat
+        */
+       public function testGetFormat( $name ) {
+               $format = RdfSerializer::getFormat( $name );
+
+               $this->assertNotNull( $format, $name );
+       }
+
+       public static function provideBuildGraphForEntity() {
+               $entities = self::getTestEntities();
+               $graphs = self::getTestGraphs();
+
+               $cases = array();
+
+               foreach ( $entities as $name => $entity ) {
+                       $cases[] = array(
+                               $entity,
+                               $graphs[$name],
+                       );
+               }
+
+               return $cases;
+       }
+
+       /**
+        * @dataProvider provideBuildGraphForEntity
+        */
+       public function testBuildGraphForEntity( Entity $entity, \EasyRdf_Graph 
$expectedGraph ) {
+               $serializer = self::newRdfSerializer( 'rdf' );
+
+               $graph = $serializer->buildGraphForEntity( $entity );
+               //TODO: meta-info from Revision
+
+               foreach ( $expectedGraph->resources() as $rc ) {
+                       foreach ( $expectedGraph->properties( $rc ) as $prop ) {
+                               $expectedValues = $expectedGraph->all( $rc, 
$prop );
+                               $actualValues = $graph->all( $rc, $prop );
+                               $this->assertArrayEquals( $expectedValues, 
$actualValues );
+                       }
+               }
+       }
+
+       public static function provideSerializeRdf() {
+               $graphs = self::getTestGraphs();
+               $patterns = self::getTestDataPatterns();
+
+               $cases = array();
+
+               foreach ( $graphs as $name => $graph ) {
+                       foreach ( self::$formats as $format ) {
+                               if ( isset( $patterns[$name][$format] ) ) {
+                                       $cases[] = array(
+                                               $graph,
+                                               $format,
+                                               $patterns[$name][$format],
+                                       );
+                               }
+                       }
+               }
+
+               return $cases;
+       }
+
+       /**
+        * @dataProvider provideSerializeRdf
+        */
+       public function testSerializeRdf( \EasyRdf_Graph $graph, $format, 
$regex ) {
+               $serializer = self::newRdfSerializer( $format );
+
+               $data = $serializer->serializeRdf( $graph );
+               $this->assertRegExp( $regex, $data );
+       }
+
+       public static function provideSerializeEntity() {
+               $entities = self::getTestEntities();
+               $patterns = self::getTestDataPatterns();
+
+               $cases = array();
+
+               foreach ( $entities as $name => $entity ) {
+                       foreach ( self::$formats as $format ) {
+                               if ( isset( $patterns[$name][$format] ) ) {
+                                       $cases[] = array(
+                                               $entity,
+                                               $format,
+                                               $patterns[$name][$format],
+                                       );
+                               }
+                       }
+               }
+
+               return $cases;
+       }
+
+       /**
+        * @dataProvider provideSerializeEntity
+        */
+       public function TestSerializeEntity( Entity $entity, $format, $regex ) {
+               $serializer = self::newRdfSerializer( $format );
+
+               $data = $serializer->serializeEntity( $entity );
+               $this->assertRegExp( $regex, $data );
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/specials/SpecialEntityDataTest.php 
b/repo/tests/phpunit/includes/specials/SpecialEntityDataTest.php
index a4d4c2c..a5e091e 100644
--- a/repo/tests/phpunit/includes/specials/SpecialEntityDataTest.php
+++ b/repo/tests/phpunit/includes/specials/SpecialEntityDataTest.php
@@ -115,6 +115,9 @@
                        ),
                        '!^a:\d+.*Raarr!', // output regex
                        200,       // http code
+                       array( // headers
+                               'Content-Type' => 
'application/vnd.php.serialized; charset=UTF-8'
+                       )
                );
 
                $cases[] = array( // #6: mime type
@@ -125,6 +128,9 @@
                        ),
                        '!^\{.*Raarr!', // output regex
                        200,       // http code
+                       array( // headers
+                               'Content-Type' => 'application/json; 
charset=UTF-8'
+                       )
                );
 
                $cases[] = array( // #7: bad format
@@ -145,6 +151,9 @@
                        ),
                        '!<entity!', // output regex
                        200,       // http code
+                       array( // headers
+                               'Content-Type' => 'text/xml; charset=UTF-8'
+                       )
                );
 
                $cases[] = array( // #9: evil stuff
@@ -158,6 +167,19 @@
                        404,  // http code
                );
 
+               $cases[] = array( // #10: RDF+XML
+                       '',      // subpage
+                       array( // parameters
+                               'id' => '{testitemid}',
+                               'format' => 'rdf',
+                       ),
+                       '!<rdf:RDF.*rdf:about.*</rdf:RDF>!s', // output regex
+                       200,       // http code
+                       array( // headers
+                               'Content-Type' => 'application/rdf+xml; 
charset=UTF-8'
+                       )
+               );
+
                $subpageCases = array();
 
                foreach ( $cases as $c ) {

-- 
To view, visit https://gerrit.wikimedia.org/r/51164
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie9f2f8944a65750077152a69923c0a0b18a49d5e
Gerrit-PatchSet: 24
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Anja Jentzsch <[email protected]>
Gerrit-Reviewer: Anja Jentzsch <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Daniel Werner <[email protected]>
Gerrit-Reviewer: Denny Vrandecic <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: John Erling Blad <[email protected]>
Gerrit-Reviewer: Tpt <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to