Daniel Werner has submitted this change and it was merged.
Change subject: Refactor RDF mapping, added support for sitelinks.
......................................................................
Refactor RDF mapping, added support for sitelinks.
Change-Id: Ibf1499d1f0742db7fbeb5cb3180552fc59cddb2d
---
M lib/includes/formatters/EntityIdFormatter.php
M repo/Wikibase.classes.php
M repo/Wikibase.hooks.php
M repo/includes/WikibaseRepo.php
A repo/includes/content/RdfBuilder.php
M repo/includes/content/RdfSerializer.php
M repo/includes/specials/SpecialEntityData.php
A repo/tests/phpunit/includes/content/RdfBuilderTest.php
M repo/tests/phpunit/includes/content/RdfSerializerTest.php
9 files changed, 882 insertions(+), 262 deletions(-)
Approvals:
Daniel Werner: Verified; Looks good to me, approved
jenkins-bot: Verified
diff --git a/lib/includes/formatters/EntityIdFormatter.php
b/lib/includes/formatters/EntityIdFormatter.php
index 0fa8b7a..61d0304 100644
--- a/lib/includes/formatters/EntityIdFormatter.php
+++ b/lib/includes/formatters/EntityIdFormatter.php
@@ -69,9 +69,9 @@
*
* @since 0.4
*
- * @param mixed $value The value to format
+ * @param EntityId $value The ID to format
*
- * @return Result
+ * @return string
* @throws InvalidArgumentException
* @throws OutOfBoundsException
*/
diff --git a/repo/Wikibase.classes.php b/repo/Wikibase.classes.php
index 3b8136c..4982b70 100644
--- a/repo/Wikibase.classes.php
+++ b/repo/Wikibase.classes.php
@@ -96,6 +96,7 @@
'Wikibase\ItemHandler' => 'includes/content/ItemHandler.php',
'Wikibase\PropertyContent' =>
'includes/content/PropertyContent.php',
'Wikibase\PropertyHandler' =>
'includes/content/PropertyHandler.php',
+ 'Wikibase\RdfBuilder' => 'includes/content/RdfBuilder.php',
'Wikibase\RdfSerializer' =>
'includes/content/RdfSerializer.php',
// EasyRdf
@@ -167,6 +168,7 @@
'Wikibase\Test\Api\LangAttributeBase' =>
'tests/phpunit/includes/api/LangAttributeBase.php',
'Wikibase\Test\EntityContentTest' =>
'tests/phpunit/includes/content/EntityContentTest.php',
'Wikibase\Test\EntityHandlerTest' =>
'tests/phpunit/includes/content/EntityHandlerTest.php',
+ 'Wikibase\Test\RdfBuilderTest' =>
'tests/phpunit/includes/content/RdfBuilderTest.php',
'MessageReporter' => 'includes/MessageReporter.php',
'ObservableMessageReporter' => 'includes/MessageReporter.php',
diff --git a/repo/Wikibase.hooks.php b/repo/Wikibase.hooks.php
index 16501ab..6905fb0 100755
--- a/repo/Wikibase.hooks.php
+++ b/repo/Wikibase.hooks.php
@@ -190,6 +190,8 @@
'content/ItemHandler',
'content/PropertyContent',
'content/PropertyHandler',
+ 'content/RdfBuilder',
+ 'content/RdfSerializer',
'specials/SpecialNewItem',
'specials/SpecialNewProperty',
diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php
index da21dc7..d63ec1b 100644
--- a/repo/includes/WikibaseRepo.php
+++ b/repo/includes/WikibaseRepo.php
@@ -3,6 +3,7 @@
namespace Wikibase\Repo;
use DataTypes\DataTypeFactory;
+use ValueFormatters\FormatterOptions;
use ValueParsers\ParserOptions;
use Wikibase\Lib\EntityIdFormatter;
use Wikibase\Lib\EntityIdLabelFormatter;
@@ -47,6 +48,11 @@
private $dataTypeFactory = null;
/**
+ * @var EntityIdFormatter|null
+ */
+ private $idFormatter = null;
+
+ /**
* @since 0.4
*
* @param SettingsArray $settings
@@ -76,6 +82,29 @@
}
/**
+ * @since 0.4
+ *
+ * @return EntityIdFormatter
+ */
+ public function getIdFormatter() {
+ if ( $this->idFormatter === null ) {
+ $prefixMap = array();
+
+ foreach ( $this->settings->getSetting( 'entityPrefixes'
) as $prefix => $entityType ) {
+ $prefixMap[$entityType] = $prefix;
+ }
+
+ $options = new FormatterOptions( array(
+ EntityIdFormatter::OPT_PREFIX_MAP => $prefixMap
+ ) );
+
+ $this->idFormatter = new EntityIdFormatter( $options );
+ }
+
+ return $this->idFormatter;
+ }
+
+ /**
* Returns the base to use when generating URIs for use in RDF output.
*
* @return string
diff --git a/repo/includes/content/RdfBuilder.php
b/repo/includes/content/RdfBuilder.php
new file mode 100755
index 0000000..60df0dd
--- /dev/null
+++ b/repo/includes/content/RdfBuilder.php
@@ -0,0 +1,485 @@
+<?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
+ * @ingroup RDF
+ *
+ * @licence GNU GPL v2+
+ * @author Anja Jentzsch < [email protected] >
+ * @author Thomas Pellissier Tanon
+ * @author Daniel Kinzler
+ */
+
+use DataTypes\DataTypeFactory;
+use DataValues\DataValue;
+use EasyRdf_Format;
+use EasyRdf_Graph;
+use EasyRdf_Namespace;
+use EasyRdf_Resource;
+use Revision;
+use Wikibase\Lib\EntityIdFormatter;
+
+class RdfBuilder {
+
+ const ONTOLOGY_BASE_URI = 'http://www.wikidata.org/ontology#'; //XXX:
Denny made me put the "www" there...
+
+ const NS_ONTOLOGY = 'wikibase'; // wikibase ontology (shared)
+ const NS_ENTITY = 'entity'; // concept uris
+ const NS_DATA = 'data'; // document uris
+ const NS_PROPERTY = 'p'; // entity -> value
+ const NS_VALUE = 'v'; // statement -> value
+ const NS_QUALIFIER = 'q'; // statement -> qualifier
+ const NS_STATEMENT = 's'; // entity -> statement
+
+ const NS_SKOS = 'skos'; // SKOS vocabulary
+ const NS_FOAF = 'foaf'; // FOAF vocabulary
+
+ const SKOS_URI = 'http://www.w3.org/2004/02/skos/core#';
+ const FOAF_URI = 'http://xmlns.com/foaf/0.1/';
+
+ const WIKIBASE_STATEMENT_QNAME = 'wikibase:Statement';
+
+ /**
+ * Map of qnames to namespace URIs
+ *
+ * @var array
+ */
+ protected $namespaces = array();
+
+ /**
+ * A list of entities mentioned/touched to or by this builder.
+ * The prefixed entity IDs are used as keys in the array, the values
'true'
+ * is used to indicate that the entity has been resolved, 'false'
indicates
+ * that the entity was mentioned but not resolved (defined).
+ *
+ * @var array
+ */
+ protected $entitiesResolved = array();
+
+ /**
+ * @param string $baseUri
+ * @param Lib\EntityIdFormatter $idFormatter
+ * @param EasyRdf_Graph|null $graph
+ */
+ public function __construct(
+ $baseUri,
+ EntityIdFormatter $idFormatter,
+ EasyRdf_Graph $graph = null
+ ) {
+ if ( !$graph ) {
+ $graph = new EasyRdf_Graph();
+ }
+
+ $this->graph = $graph;
+ $this->baseUri = $baseUri;
+ $this->idFormatter = $idFormatter;
+
+ $this->namespaces = array(
+ self::NS_ONTOLOGY => self::ONTOLOGY_BASE_URI,
+ self::NS_DATA => $this->baseUri . '/data/',
+ self::NS_ENTITY => $this->baseUri . '/entity/',
+ self::NS_PROPERTY => $this->baseUri . '/property/',
+ self::NS_VALUE => $this->baseUri . '/value/',
+ self::NS_QUALIFIER => $this->baseUri . '/qualifier/',
+ self::NS_STATEMENT => $this->baseUri . '/statement/',
+ self::NS_SKOS => self::SKOS_URI,
+ self::NS_FOAF => self::FOAF_URI,
+ );
+
+ //XXX: Ugh, static. Should go into $this->graph.
+ foreach ( $this->getNamespaces() as $gname => $uri ) {
+ EasyRdf_Namespace::set( $gname, $uri );
+ }
+ }
+
+ /**
+ * 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 the builder's graph
+ *
+ * @return EasyRdf_Graph
+ */
+ public function getGraph() {
+ return $this->graph;
+ }
+
+ /**
+ * Returns a map of namespace names to URIs
+ *
+ * @return array
+ */
+ public function getNamespaces() {
+ return $this->namespaces;
+ }
+
+ /**
+ * Returns a qname for the given entity using the given prefix.
+ * To refer to the entity as such, use self::NS_ENTITY.
+ * To refer to the description of the entity, use NS_DATA.
+ * For the different prefixes used for properties in statements,
+ * refer to the Wikibase RDF mapping spec.
+ *
+ * @param string $prefix use a self::NS_XXX constant
+ * @param EntityId $id
+ *
+ * @return string
+ */
+ public function getEntityQName( $prefix, EntityId $id ) {
+ return $prefix . ':' . $this->idFormatter->format( $id );
+ }
+
+ /**
+ * Returns a qname for the given statement using the given prefix.
+ *
+ * @param string $prefix use a self::NS_XXX constant, usually
self::NS_STATEMENT
+ * @param Claim $claim
+ *
+ * @return string
+ */
+ public function getStatementQName( $prefix, Claim $claim ) {
+ return $prefix . ':' . $claim->getGuid();
+ }
+
+ /**
+ * Returns a qname for the given entity type.
+ * For well known types, these qnames refer to classes from the
Wikibase ontology.
+ *
+ * @param $type
+ *
+ * @return string
+ */
+ public function getEntityTypeQName( $type ) {
+ //TODO: the list of types is configurable, need to register
URIs for extra types!
+
+ return self::NS_ONTOLOGY . ':' . ucfirst( $type );
+ }
+
+ /**
+ * Gets a resource object representing the given entity
+ *
+ * @param EntityId $id
+ *
+ * @return EasyRDF_Resource
+ */
+ public function getEntityResource( EntityId $id ) {
+ $entityQName = $this->getEntityQName( self::NS_ENTITY, $id );
+ $entityResource = $this->graph->resource( $entityQName );
+ return $entityResource;
+ }
+
+ /**
+ * Language filter
+ *
+ * @param $lang
+ *
+ * @return bool
+ */
+ protected function isLanguageIncluded( $lang ) {
+ return true; //todo: optional filter
+ }
+
+ /**
+ * Registers an entity as mentioned. Will be recorded as unresolved
+ * if it wasn't already marked as resolved.
+ *
+ * @param EntityId $id
+ */
+ protected function entityMentioned( EntityId $id ) {
+ $prefixedId = $this->idFormatter->format( $id );
+
+ if ( !isset( $this->entitiesResolved[$prefixedId] ) ) {
+ $this->entitiesResolved[$prefixedId] = false;
+ }
+ }
+
+ /**
+ * Registers an entity as resolved.
+ *
+ * @param EntityId $id
+ */
+ protected function entityResolved( EntityId $id ) {
+ $prefixedId = $this->idFormatter->format( $id );
+ $this->entitiesResolved[$prefixedId] = true;
+ }
+
+ /**
+ * Adds meta-information about an entity (such as the ID and type) to
the RDF graph.
+ *
+ * @param Entity $entity
+ * @param \Revision|null $rev
+ */
+ public function addEntityMetaData( Entity $entity, Revision $rev = null
) {
+ $entityResource = $this->getEntityResource( $entity->getId() );
+ $entityResource->addResource( 'rdf:type',
$this->getEntityTypeQName( $entity->getType() ) );
+ $entityResource->addResource( 'foaf:primaryTopicOf',
$this->getEntityQName( self::NS_DATA, $entity->getId() ) );
+
+ //TODO: revision timestamp, revision id, versioned data URI
+
+ $this->entityResolved( $entity->getId() );
+ }
+
+ /**
+ * Adds the labels of the given entity to the RDF graph
+ *
+ * @param Entity $entity
+ */
+ public function addLabels( Entity $entity ) {
+ $entityResource = $this->getEntityResource( $entity->getId() );
+
+ foreach ( $entity->getLabels() as $languageCode => $labelText )
{
+ if ( !$this->isLanguageIncluded( $languageCode ) ) {
+ continue;
+ }
+
+ $entityResource->addLiteral( 'rdfs:label', $labelText,
$languageCode );
+ $entityResource->addLiteral( 'skos:prefLabel',
$labelText, $languageCode );
+ }
+ }
+
+ /**
+ * Adds the descriptions of the given entity to the RDF graph.
+ *
+ * @param Entity $entity
+ */
+ public function addDescriptions( Entity $entity ) {
+ $entityResource = $this->getEntityResource( $entity->getId() );
+
+ foreach ( $entity->getDescriptions() as $languageCode =>
$description ) {
+ if ( !$this->isLanguageIncluded( $languageCode ) ) {
+ continue;
+ }
+
+ $entityResource->addLiteral( 'skos:note', $description,
$languageCode );
+ }
+ }
+
+ /**
+ * Adds the aliases of the given entity to the RDF graph.
+ *
+ * @param Entity $entity
+ */
+ public function addAliases( Entity $entity ) {
+ $entityResource = $this->getEntityResource( $entity->getId() );
+
+ foreach ( $entity->getAllAliases() as $languageCode => $aliases
) {
+ if ( !$this->isLanguageIncluded( $languageCode ) ) {
+ continue;
+ }
+
+ foreach ( $aliases as $alias ) {
+ $entityResource->addLiteral( 'skos:altLabel',
$alias, $languageCode );
+ }
+ }
+ }
+
+ /**
+ * Adds the site links of the given item to the RDF graph.
+ *
+ * @param Item $item
+ */
+ public function addSiteLinks( Item $item ) {
+ $entityResource = $this->getEntityResource( $item->getId() );
+
+ /* @var SiteLink $link */
+ foreach ( $item->getSiteLinks() as $link ) {
+ $languageCode = $link->getSite()->getLanguageCode();
+
+ if ( !$this->isLanguageIncluded( $languageCode ) ) {
+ continue;
+ }
+
+ $pageRecourse = $this->graph->resource( $link->getUrl()
);
+ $entityResource->addResource( 'foaf:primaryTopicOf',
$pageRecourse, $languageCode );
+ }
+ }
+
+ /**
+ * Adds all Claims/Statements from the given entity to the RDF graph.
+ *
+ * @param Entity $entity
+ */
+ public function addClaims( Entity $entity ) {
+ /* @var Claim $claim */
+ foreach( $entity->getClaims() as $claim ) {
+ $this->addClaim( $entity, $claim );
+ }
+ }
+
+ /**
+ * Adds the given Claim from the given Entity to the RDF graph.
+ *
+ * @param Entity $entity
+ * @param Claim $claim
+ */
+ public function addClaim( Entity $entity, Claim $claim ) {
+ $this->addMainSnak( $entity, $claim );
+
+ //TODO: add qualifiers
+ //TODO: add references
+ }
+
+ /**
+ * Adds the given Claim's main Snak to the RDF graph.
+ *
+ * @param Entity $entity
+ * @param Claim $claim
+ */
+ public function addMainSnak( Entity $entity, Claim $claim ) {
+ $snak = $claim->getMainSnak();
+
+ if ( $snak instanceof PropertyValueSnak ) {
+ $this->addPropertyValueSnak( $entity, $claim, $snak );
+ } else {
+ //TODO: NoValueSnak, SomeValueSnak
+ wfWarn( "Unsupported snak type: " . get_class( $snak )
);
+ }
+ }
+
+
+ /**
+ * Returns a resource representing the given claim.
+ *
+ * @param Claim $claim
+ *
+ * @return EasyRDF_Resource
+ */
+ public function getStatementResource( Claim $claim ) {
+ $statementQName = $this->getStatementQName( self::NS_STATEMENT,
$claim );
+ $statementResource = $this->graph->resource( $statementQName,
array( self::WIKIBASE_STATEMENT_QNAME ) );
+ return $statementResource;
+ }
+
+ /**
+ * Adds the given PropertyValueSnak to the RDF graph.
+ *
+ * @param Entity $entity
+ * @param PropertyValueSnak $snak
+ * @param Claim $claim
+ */
+ public function addPropertyValueSnak( Entity $entity, Claim $claim,
PropertyValueSnak $snak ) {
+ $entityResource = $this->getEntityResource( $entity->getId() );
+
+ $propertyId = $claim->getMainSnak()->getPropertyId();
+ $propertyQName = $this->getEntityQName( self::NS_PROPERTY,
$propertyId );
+
+ $statementResource = $this->getStatementResource( $claim );
+ $entityResource->addResource( $propertyQName,
$statementResource );
+
+ $value = $snak->getDataValue()->getValue();
+
+ $this->entityMentioned( $propertyId );
+ $this->addClaimValue( $claim, $propertyId, $value );
+ }
+
+ /**
+ * Adds the value of the given property to the RDF graph.
+ *
+ * @param Claim $claim
+ * @param EntityId $propertyId
+ * @param DataValue $value
+ */
+ public function addClaimValue( Claim $claim, EntityId $propertyId,
DataValue $value ) {
+ $statementResource = $this->getStatementResource( $claim );
+ $propertyValueQName = $this->getEntityQName( self::NS_VALUE,
$propertyId );
+
+ $typeId = $value->getType();
+
+ switch ( $typeId ) {
+ case 'wikibase-item':
+ $rawValue = $value->getValue();
+
+ assert( $rawValue instanceof EntityId );
+ $valueQName = $this->getEntityQName(
self::NS_ENTITY, $rawValue );
+ $valueResource = $this->graph->resource(
$valueQName );
+ $statementResource->addResource(
$propertyValueQName, $valueResource );
+ $this->entityMentioned( $rawValue );
+ break;
+ case 'commonsMedia':
+ $statementResource->addResource(
$propertyValueQName, $value );
+ break;
+ default:
+ //TODO: more media types
+ wfWarn( "Unsupported data type: $typeId" );
+ }
+ }
+
+ /**
+ * Add stubs for any entities that were previously mentioned (e.g. as
properties
+ * or data values).
+ *
+ * @param EntityLookup $entityLookup
+ */
+ public function resolvedMentionedEntities( EntityLookup $entityLookup )
{
+ foreach ( $this->entitiesResolved as $id => $resolved ) {
+ if ( !$resolved ) {
+ $id = EntityId::newFromPrefixedId( $id );
+ $entity = $entityLookup->getEntity( $id );
+
+ $this->addEntityStub( $entity );
+ }
+ }
+ }
+
+ /**
+ * Add an entity to the RDF graph, including all supported structural
components
+ * of the entity.
+ *
+ * @param Entity $entity the entity to output.
+ * @param \Revision $revision for meta data (optional)
+ */
+ public function addEntity( Entity $entity, \Revision $revision = null )
{
+ $this->addEntityMetaData( $entity, $revision );
+
+ $this->addLabels( $entity );
+ $this->addDescriptions( $entity );
+ $this->addAliases( $entity );
+
+ if ( $entity instanceof Item ) {
+ $this->addSiteLinks( $entity );
+ }
+
+ $this->addClaims( $entity );
+ }
+
+ /**
+ * Adds stub information for the given Entity to the RDF graph. Stub
information
+ * means meta information and labels.
+ *
+ * @param Entity $entity
+ */
+ public function addEntityStub( Entity $entity ) {
+ $this->addEntityMetaData( $entity );
+ $this->addLabels( $entity );
+ $this->addDescriptions( $entity );
+ }
+}
diff --git a/repo/includes/content/RdfSerializer.php
b/repo/includes/content/RdfSerializer.php
index 01c3e36..54f5c3d 100755
--- a/repo/includes/content/RdfSerializer.php
+++ b/repo/includes/content/RdfSerializer.php
@@ -3,7 +3,7 @@
namespace Wikibase;
/**
- * RDF mapping for wikibase data model.
+ * RDF serialization 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
@@ -25,6 +25,7 @@
* @file
* @ingroup WikibaseRepo
* @ingroup Content
+ * @ingroup RDF
*
* @licence GNU GPL v2+
* @author Anja Jentzsch < [email protected] >
@@ -37,13 +38,13 @@
use EasyRdf_Format;
use EasyRdf_Graph;
use EasyRdf_Namespace;
+use Wikibase\Lib\EntityIdFormatter;
class RdfSerializer {
- const ontologyBaseUri = 'http://www.wikidata.org'; //XXX: Denny made me
put the "www" there...
-
/**
* @var DataTypeFactory
+ * @note: currently unused. keep?
*/
protected $dataTypeFactory;
@@ -58,47 +59,31 @@
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
+ * @param EasyRdf_Format $format
+ * @param string $uriBase
+ * @param EntityLookup $entityLookup
+ * @param DataTypeFactory $dataTypeFactory
+ * @param Lib\EntityIdFormatter $idFormatter
*/
public function __construct(
EasyRdf_Format $format,
$uriBase,
EntityLookup $entityLookup,
- DataTypeFactory $dataTypeFactory
+ DataTypeFactory $dataTypeFactory,
+ EntityIdFormatter $idFormatter
) {
$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/',
- );
+ $this->idFormatter = $idFormatter;
}
/**
* 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' );
+ return RdfBuilder::isSupported();
}
/**
@@ -109,7 +94,7 @@
* 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.
+ * @param string $name the name (file extension, mime type) of the
desired format.
*
* @return EasyRdf_Format|null the format object, or null if not found.
*/
@@ -130,26 +115,27 @@
}
public function getNamespaces() {
- return $this->namespaces;
+ return $this->newRdfBuilder()->getNamespaces(); //XXX: nasty
hack!
}
/**
- * Inits a new Graph
+ * Creates a new builder
*
- * @return EasyRdf_Graph
+ * @return RdfBuilder
*/
- protected function newRdfGraph() {
- //register namespaces (ugh, static crap)
+ public function newRdfBuilder() {
+ //TODO: language filter
- foreach ( $this->getNamespaces() as $gname => $uri ) {
- EasyRdf_Namespace::set( $gname, $uri );
- }
+ $builder = new RdfBuilder(
+ $this->uriBase,
+ $this->idFormatter
+ );
- return new EasyRdf_Graph();
+ return $builder;
}
/**
- * Add an entity to the RDF graph
+ * Generates an RDF graph representing the given entity
*
* @param Entity $entity the entity to output.
* @param \Revision $revision for meta data (optional)
@@ -157,82 +143,12 @@
* @return EasyRdf_Graph
*/
public function buildGraphForEntity( Entity $entity, \Revision
$revision = null ) {
- $graph = $this->newRdfGraph();
+ $builder = $this->newRdfBuilder();
- $entityUri = 'wikibase_id:' . ucfirst(
$entity->getId()->getPrefixedId() );
- $entityResource = $graph->resource( $entityUri );
+ $builder->addEntity( $entity, $revision );
+ $builder->resolvedMentionedEntities( $this->entityLookup );
//TODO: optional
- //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
-
+ $graph = $builder->getGraph();
return $graph;
}
@@ -266,6 +182,9 @@
return $data;
}
+ /**
+ * @return string
+ */
public function getDefaultMimeType() {
return $this->format->getDefaultMimeType();
}
diff --git a/repo/includes/specials/SpecialEntityData.php
b/repo/includes/specials/SpecialEntityData.php
index 7b35bc1..fa0a084 100755
--- a/repo/includes/specials/SpecialEntityData.php
+++ b/repo/includes/specials/SpecialEntityData.php
@@ -63,6 +63,11 @@
protected $dataTypeFactory = null;
/**
+ * @var \Wikibase\Lib\EntityIdFormatter
+ */
+ protected $idFormatter = null;
+
+ /**
* Constructor.
*
* @since 0.4
@@ -112,7 +117,7 @@
$this->rdfBaseURI = $repo->getRdfBaseURI();
$this->entityLookup =
\Wikibase\StoreFactory::getStore()->getEntityLookup();
$this->dataTypeFactory = $repo->getDataTypeFactory();
-
+ $this->idFormatter = $repo->getIdFormatter();
$this->showData( $format, $id, $revision );
}
@@ -291,7 +296,8 @@
$rdfFormat,
$this->rdfBaseURI,
$this->entityLookup,
- $this->dataTypeFactory
+ $this->dataTypeFactory,
+ $this->idFormatter
);
return $serializer;
diff --git a/repo/tests/phpunit/includes/content/RdfBuilderTest.php
b/repo/tests/phpunit/includes/content/RdfBuilderTest.php
new file mode 100644
index 0000000..47827ad
--- /dev/null
+++ b/repo/tests/phpunit/includes/content/RdfBuilderTest.php
@@ -0,0 +1,271 @@
+<?php
+
+namespace Wikibase\Test;
+use DataTypes\DataTypeFactory;
+use EasyRdf_Namespace;
+use MediaWikiSite;
+use ValueFormatters\FormatterOptions;
+use Wikibase\Entity;
+use Wikibase\EntityId;
+use Wikibase\Item;
+use Wikibase\Lib\EntityIdFormatter;
+use Wikibase\Property;
+use Wikibase\RdfBuilder;
+
+/**
+ * Tests for the Wikibase\RdfBuilder 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
+ * @ingroup RDF
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class RdfBuilderTest extends \MediaWikiTestCase {
+
+ const URI_BASE = 'http://acme.test';
+
+ public function setUp() {
+ parent::setUp();
+
+ if ( !RdfBuilder::isSupported() ) {
+ $this->markTestSkipped( "RDF library not found" );
+ }
+ }
+
+ /**
+ * @return Entity[]
+ */
+ public 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' ) );
+
+ // TODO: test links
+ // TODO: test data values
+
+ $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();
+
+ $builder = self::newRdfBuilder( 'rdf' ); //XXX: ugh, dummy
object
+
+ $entityUri = $builder->getEntityQName( RdfBuilder::NS_ENTITY,
$entityId );
+ $entityResource = $graph->resource( $entityUri );
+
+ /* @var \EasyRdf_Resource $entityResource */
+ foreach ( $properties as $prop => $values ) {
+ if ( is_scalar( $values ) ) {
+ $values = array( $values );
+ }
+
+ foreach ( $values as $val ) {
+ if ( is_string( $val ) ) {
+ $val = $graph->resource( $val );
+ }
+
+ $entityResource->add( $prop, $val );
+ }
+ }
+
+ return $graph;
+ }
+
+ /**
+ * @return \EasyRdf_Graph[]
+ */
+ public static function getTestGraphs() {
+ static $graphs = array();
+
+ if ( !empty( $graphs ) ) {
+ return $graphs;
+ }
+
+ $builder = self::newRdfBuilder( 'rdf' ); //XXX: ugh, dummy
object
+
+ foreach ( $builder->getNamespaces() as $gname => $uri ) {
+ EasyRdf_Namespace::set( $gname, $uri );
+ }
+
+ $entities = self::getTestEntities();
+
+ $graphs['empty'] = self::makeEntityGraph(
+ $entities['empty']->getId(),
+ array(
+ 'rdf:type' => $builder->getEntityTypeQName(
Item::ENTITY_TYPE ),
+ )
+ );
+
+ $graphs['terms'] = self::makeEntityGraph(
+ $entities['terms']->getId(),
+ array(
+ 'rdf:type' => $builder->getEntityTypeQName(
Item::ENTITY_TYPE ),
+ 'foaf:primaryTopicOf' =>
$builder->getEntityQName( RdfBuilder::NS_DATA, $entities['terms']->getId() ),
+ 'rdfs:label' => array(
+ new \EasyRdf_Literal( 'Berlin', 'en' ),
+ new \EasyRdf_Literal( 'Берлин', 'ru' )
+ ),
+ 'skos:prefLabel' => array(
+ new \EasyRdf_Literal( 'Berlin', 'en' ),
+ new \EasyRdf_Literal( 'Берлин', 'ru' )
+ ),
+ 'skos:note' => array(
+ new \EasyRdf_Literal( 'German city',
'en' ),
+ new \EasyRdf_Literal( 'столица и
одновременно земля Германии', 'ru' )
+ ),
+ 'skos:altLabel' => array(
+ new \EasyRdf_Literal( 'Berlin,
Germany', 'en' ),
+ new \EasyRdf_Literal( 'Land Berlin',
'en' ),
+ new \EasyRdf_Literal( 'Berlin', 'ru' )
+ ),
+ )
+ );
+
+ // TODO: test links
+ // TODO: test data values
+
+ return $graphs;
+ }
+
+ /**
+ * @return RdfBuilder
+ */
+ protected static function newRdfBuilder() {
+ $idFormatter = new EntityIdFormatter( new FormatterOptions(
array(
+ EntityIdFormatter::OPT_PREFIX_MAP => array(
+ Item::ENTITY_TYPE => 'q',
+ Property::ENTITY_TYPE => 'p',
+ )
+ ) ) );
+
+ return new RdfBuilder(
+ self::URI_BASE,
+ $idFormatter
+ );
+ }
+
+ public static function provideAddEntity() {
+ $entities = self::getTestEntities();
+ $graphs = self::getTestGraphs();
+
+ $cases = array();
+
+ foreach ( $entities as $name => $entity ) {
+ $cases[] = array(
+ $entity,
+ $graphs[$name],
+ );
+ }
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideAddEntity
+ */
+ public function testAddEntity( Entity $entity, \EasyRdf_Graph
$expectedGraph ) {
+ $builder = $this->newRdfBuilder();
+
+ //TODO: also test revision meta data
+ $builder->addEntity( $entity );
+ $graph = $builder->getGraph();
+
+ foreach ( $expectedGraph->resources() as $rc ) {
+ foreach ( $expectedGraph->properties( $rc ) as $prop ) {
+ $expectedValues = $expectedGraph->all( $rc,
$prop );
+ $actualValues = $graph->all( $rc, $prop );
+
+ $this->assertArrayEquals(
+ self::rdf2strings( $expectedValues ),
+ self::rdf2strings( $actualValues )
+ );
+ }
+ }
+ }
+
+ public static function rdf2strings( array $data ) {
+ $strings = array();
+
+ foreach ( $data as $obj ) {
+ $strings[] = self::rdf2string( $obj );
+ }
+
+ return $strings;
+ }
+
+ public static function rdf2string( $obj ) {
+ if ( $obj instanceof \EasyRdf_Resource ) {
+ return '<' . $obj->getUri() . '>';
+ } elseif ( $obj instanceof \EasyRdf_Literal ) {
+ $s = '"' . $obj->getValue() . '"';
+
+ if ( $obj->getDatatype() ) {
+ $s .= '^^' . $obj->getDatatype();
+ } elseif ( $obj->getLang() ) {
+ $s .= '@' . $obj->getLang();
+ }
+
+ return $s;
+ } else {
+ return strval( $obj );
+ }
+ }
+
+ //TODO: test resolveMentionedEntities
+ //TODO: test all the addXXX methods
+
+}
diff --git a/repo/tests/phpunit/includes/content/RdfSerializerTest.php
b/repo/tests/phpunit/includes/content/RdfSerializerTest.php
index 7de00d5..9136029 100644
--- a/repo/tests/phpunit/includes/content/RdfSerializerTest.php
+++ b/repo/tests/phpunit/includes/content/RdfSerializerTest.php
@@ -4,9 +4,12 @@
use DataTypes\DataTypeFactory;
use EasyRdf_Namespace;
use MediaWikiSite;
+use ValueFormatters\FormatterOptions;
use Wikibase\Entity;
use Wikibase\EntityId;
use Wikibase\Item;
+use Wikibase\Lib\EntityIdFormatter;
+use Wikibase\Property;
use Wikibase\RdfSerializer;
use Wikibase\SiteLink;
@@ -33,12 +36,11 @@
*
* @ingroup WikibaseRepoTest
* @ingroup Test
+ * @ingroup RDF
*
* @group Wikibase
* @group WikibaseRepo
- * @group WikibaseProperty
- * @group WikibaseEntity
- * @group WikibaseEntityHandler
+ * @group WikibaseRdf
*
* @licence GNU GPL v2+
* @author Daniel Kinzler
@@ -79,8 +81,6 @@
),
);
- protected static $uriBase = 'http://acme.test';
-
public function setUp() {
parent::setUp();
@@ -93,127 +93,14 @@
* @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 RdfBuilderTest::getTestEntities();
}
/**
* @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;
+ return RdfBuilderTest::getTestGraphs();
}
protected static function getTestDataPatterns() {
@@ -223,26 +110,28 @@
return $patterns;
}
- $patterns['empty']['rdf'] = '!<rdf:RDF.*</rdf:RDF>!s';
- $patterns['empty']['n3'] = '!!s';
+ $patterns['empty']['rdf'] = array( '!<rdf:RDF.*</rdf:RDF>!s' );
+ $patterns['empty']['n3'] = array( '!!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';
+ $patterns['terms']['rdf'] = array(
+ '!<rdf:RDF.*</rdf:RDF>!s',
+ '!<wikibase:Item.*rdf:about=".*?entity/q2"!s',
+ '!<rdfs:label xml:lang="en">Berlin</rdfs:label>!s',
+ '!<skos:prefLabel
xml:lang="en">Berlin</skos:prefLabel>!s',
+ '!<skos:note xml:lang="en">German city</skos:note>!s',
+ '!<skos:altLabel xml:lang="en">Berlin,
Germany</skos:altLabel>!s',
+ );
- // links not yet supported
- /*
- $patterns['links']['rdf'] = '!xxx!s';
- $patterns['links']['n3'] = '!xxx!s';
- */
+ $patterns['terms']['n3'] = array(
+ '!entity:q2!s',
+ '!rdfs:label +"Berlin"@en,!s',
+ '!skos:prefLabel +"Berlin"@en,!s',
+ '!skos:note +"German city"@en,!s',
+ '!skos:altLabel +"Berlin, Germany"@en,!s',
+ );
+
+ // TODO: test links
+ // TODO: test data values
return $patterns;
}
@@ -252,6 +141,12 @@
$format = RdfSerializer::getFormat( $formatName );
$dataTypes = new DataTypeFactory( self::$dataTypes );
+ $idSerializer = new EntityIdFormatter( new FormatterOptions(
array(
+ EntityIdFormatter::OPT_PREFIX_MAP => array(
+ Item::ENTITY_TYPE => 'q',
+ Property::ENTITY_TYPE => 'p',
+ )
+ ) ) );
$mockRepo = new MockRepository();
@@ -261,9 +156,10 @@
return new RdfSerializer(
$format,
- self::$uriBase,
+ RdfBuilderTest::URI_BASE,
$mockRepo,
- $dataTypes
+ $dataTypes,
+ $idSerializer
);
}
@@ -314,7 +210,11 @@
foreach ( $expectedGraph->properties( $rc ) as $prop ) {
$expectedValues = $expectedGraph->all( $rc,
$prop );
$actualValues = $graph->all( $rc, $prop );
- $this->assertArrayEquals( $expectedValues,
$actualValues );
+
+ $this->assertArrayEquals(
+ RdfBuilderTest::rdf2strings(
$expectedValues ),
+ RdfBuilderTest::rdf2strings(
$actualValues )
+ );
}
}
}
@@ -343,11 +243,14 @@
/**
* @dataProvider provideSerializeRdf
*/
- public function testSerializeRdf( \EasyRdf_Graph $graph, $format,
$regex ) {
+ public function testSerializeRdf( \EasyRdf_Graph $graph, $format,
$regexes ) {
$serializer = self::newRdfSerializer( $format );
$data = $serializer->serializeRdf( $graph );
- $this->assertRegExp( $regex, $data );
+
+ foreach ( $regexes as $regex ) {
+ $this->assertRegExp( $regex, $data );
+ }
}
public static function provideSerializeEntity() {
@@ -374,11 +277,14 @@
/**
* @dataProvider provideSerializeEntity
*/
- public function TestSerializeEntity( Entity $entity, $format, $regex ) {
+ public function testSerializeEntity( Entity $entity, $format, $regexes
) {
$serializer = self::newRdfSerializer( $format );
$data = $serializer->serializeEntity( $entity );
- $this->assertRegExp( $regex, $data );
+
+ foreach ( $regexes as $regex ) {
+ $this->assertRegExp( $regex, $data );
+ }
}
}
--
To view, visit https://gerrit.wikimedia.org/r/61471
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ibf1499d1f0742db7fbeb5cb3180552fc59cddb2d
Gerrit-PatchSet: 5
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Daniel Werner <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits