jenkins-bot has submitted this change and it was merged.

Change subject: Split SimpleValueRdfBuilder and ComplexValueRdfBuilder
......................................................................


Split SimpleValueRdfBuilder and ComplexValueRdfBuilder

This replaces the monolithic implementation of the RDF mapping
for data values with individual classes for each data type.

NOTE: SimpleValueRdfBuilderTest and ComplexValueRdfBuilderTest
are kept to make sure that all previous test cases still pass.
They should now be redundant though, and can be removed in
a future patch.

Bug: T118500
Change-Id: I6d9b99d657e3dcd1de9072ba5582c3f05a99523b
---
M lib/includes/DataTypeDefinitions.php
M repo/WikibaseRepo.datatypes.php
D repo/includes/rdf/ComplexValueRdfBuilder.php
M repo/includes/rdf/DispatchingValueSnakRdfBuilder.php
D repo/includes/rdf/SimpleValueRdfBuilder.php
A repo/includes/rdf/Values/CommonsMediaRdfBuilder.php
A repo/includes/rdf/Values/ComplexValueRdfHelper.php
A repo/includes/rdf/Values/EntityIdRdfBuilder.php
A repo/includes/rdf/Values/GlobeCoordinateRdfBuilder.php
A repo/includes/rdf/Values/LiteralValueRdfBuilder.php
A repo/includes/rdf/Values/MonolingualTextRdfBuilder.php
A repo/includes/rdf/Values/ObjectUriRdfBuilder.php
A repo/includes/rdf/Values/QuantityRdfBuilder.php
A repo/includes/rdf/Values/TimeRdfBuilder.php
R repo/tests/phpunit/data/rdf/RdfDumpGenerator/empty.nt
R repo/tests/phpunit/data/rdf/RdfDumpGenerator/entities.nt
R repo/tests/phpunit/data/rdf/RdfDumpGenerator/redirect.nt
R repo/tests/phpunit/data/rdf/RdfDumpGenerator/refs.nt
M repo/tests/phpunit/includes/Dumpers/RdfDumpGeneratorTest.php
M repo/tests/phpunit/includes/LinkedData/EntityDataSerializationServiceTest.php
M repo/tests/phpunit/includes/rdf/ComplexValueRdfBuilderTest.php
M repo/tests/phpunit/includes/rdf/SimpleValueRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/CommonsMediaRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/ComplexValueRdfHelperTest.php
A repo/tests/phpunit/includes/rdf/Values/EntityIdRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/GlobeCoordinateRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/LiteralValueRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/MonolingualTextRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/ObjectValueRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/QuantityRdfBuilderTest.php
A repo/tests/phpunit/includes/rdf/Values/TimeRdfBuilderTest.php
31 files changed, 1,576 insertions(+), 524 deletions(-)

Approvals:
  Thiemo Mättig (WMDE): Looks good to me, approved
  jenkins-bot: Verified



diff --git a/lib/includes/DataTypeDefinitions.php 
b/lib/includes/DataTypeDefinitions.php
index e68538f..64b4184 100644
--- a/lib/includes/DataTypeDefinitions.php
+++ b/lib/includes/DataTypeDefinitions.php
@@ -125,7 +125,7 @@
        }
 
        /**
-        * @see DataValueRdfBuilderFactory
+        * @see ValueSnakRdfBuilderFactory
         *
         * @return callable[]
         */
diff --git a/repo/WikibaseRepo.datatypes.php b/repo/WikibaseRepo.datatypes.php
index b71fe42..132827b 100644
--- a/repo/WikibaseRepo.datatypes.php
+++ b/repo/WikibaseRepo.datatypes.php
@@ -28,11 +28,19 @@
 use ValueParsers\QuantityParser;
 use ValueParsers\StringParser;
 use ValueParsers\ValueParser;
-use Wikibase\Rdf\ComplexValueRdfBuilder;
 use Wikibase\Rdf\DedupeBag;
 use Wikibase\Rdf\EntityMentionListener;
+use Wikibase\Rdf\JulianDateTimeValueCleaner;
 use Wikibase\Rdf\RdfVocabulary;
-use Wikibase\Rdf\SimpleValueRdfBuilder;
+use Wikibase\Rdf\Values\CommonsMediaRdfBuilder;
+use Wikibase\Rdf\Values\ComplexValueRdfHelper;
+use Wikibase\Rdf\Values\EntityIdRdfBuilder;
+use Wikibase\Rdf\Values\GlobeCoordinateRdfBuilder;
+use Wikibase\Rdf\Values\LiteralValueRdfBuilder;
+use Wikibase\Rdf\Values\MonolingualTextRdfBuilder;
+use Wikibase\Rdf\Values\ObjectUriRdfBuilder;
+use Wikibase\Rdf\Values\QuantityRdfBuilder;
+use Wikibase\Rdf\Values\TimeRdfBuilder;
 use Wikibase\Repo\Parsers\EntityIdValueParser;
 use Wikibase\Repo\Parsers\MediaWikiNumberUnlocalizer;
 use Wikibase\Repo\Parsers\MonolingualTextParser;
@@ -65,27 +73,6 @@
                return new StringParser( $normalizer );
        };
 
-       // XXX: Use the same constructor for the RDF mapping of all datatypes 
for now.
-       //      Implementation will be split in the future.
-       $rdfBuilderContructor = function (
-               $mode,
-               RdfVocabulary $vocab,
-               RdfWriter $writer,
-               EntityMentionListener $tracker,
-               DedupeBag $dedupe
-       ) {
-               if ( $mode === 'simple' ) {
-                       $builder = new SimpleValueRdfBuilder( $vocab );
-               } else {
-                       $builder = new ComplexValueRdfBuilder( $vocab, 
$writer->sub() );
-                       $builder->setDedupeBag( $dedupe );
-               }
-
-               $builder->setEntityMentionListener( $tracker );
-
-               return $builder;
-       };
-
        return array(
                'commonsMedia' => array(
                        'validator-factory-callback' => function() {
@@ -97,7 +84,15 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newCommonsMediaFormatter( 
$format, $options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new CommonsMediaRdfBuilder( $vocab );
+                       },
                ),
                'globe-coordinate' => array(
                        'validator-factory-callback' => function() {
@@ -111,7 +106,16 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newGlobeCoordinateFormatter( 
$format, $options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               $complexValueHelper = $mode === 'simple' ? null 
: new ComplexValueRdfHelper( $vocab, $writer->sub(), $dedupe );
+                               return new GlobeCoordinateRdfBuilder( 
$complexValueHelper );
+                       },
                ),
                'monolingualtext' => array(
                        'validator-factory-callback' => function() {
@@ -125,7 +129,15 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newMonolingualFormatter( 
$format, $options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new MonolingualTextRdfBuilder();
+                       },
                ),
                'quantity' => array(
                        'validator-factory-callback' => function() {
@@ -141,7 +153,16 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newQuantityFormatter( $format, 
$options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               $complexValueHelper = $mode === 'simple' ? null 
: new ComplexValueRdfHelper( $vocab, $writer->sub(), $dedupe );
+                               return new QuantityRdfBuilder( 
$complexValueHelper );
+                       },
                ),
                'string' => array(
                        'validator-factory-callback' => function() {
@@ -152,7 +173,15 @@
                        'formatter-factory-callback' => function( $format, 
FormatterOptions $options ) {
                                return null; // rely on formatter for string 
value type
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new LiteralValueRdfBuilder( null, null );
+                       },
                ),
                'time' => array(
                        'validator-factory-callback' => function() {
@@ -167,7 +196,18 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newTimeFormatter( $format, 
$options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               // TODO: if data is fixed to be always 
Gregorian, replace with DateTimeValueCleaner
+                               $dateCleaner = new JulianDateTimeValueCleaner();
+                               $complexValueHelper = $mode === 'simple' ? null 
: new ComplexValueRdfHelper( $vocab, $writer->sub(), $dedupe );
+                               return new TimeRdfBuilder( $dateCleaner, 
$complexValueHelper );
+                       },
                ),
                'url' => array(
                        'validator-factory-callback' => function() {
@@ -179,7 +219,15 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newUrlFormatter( $format, 
$options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new ObjectUriRdfBuilder();
+                       },
                ),
                'wikibase-item' => array(
                        'validator-factory-callback' => function() {
@@ -191,7 +239,15 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newEntityIdFormatter( $format, 
$options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new EntityIdRdfBuilder( $vocab, $tracker 
);
+                       },
                ),
                'wikibase-property' => array(
                        'validator-factory-callback' => function() {
@@ -203,7 +259,15 @@
                                $factory = 
WikibaseRepo::getDefaultFormatterBuilders();
                                return $factory->newEntityIdFormatter( $format, 
$options );
                        },
-                       'rdf-builder-factory-callback' => $rdfBuilderContructor,
+                       'rdf-builder-factory-callback' => function (
+                               $mode,
+                               RdfVocabulary $vocab,
+                               RdfWriter $writer,
+                               EntityMentionListener $tracker,
+                               DedupeBag $dedupe
+                       ) {
+                               return new EntityIdRdfBuilder( $vocab, $tracker 
);
+                       },
                ),
        );
 
diff --git a/repo/includes/rdf/ComplexValueRdfBuilder.php 
b/repo/includes/rdf/ComplexValueRdfBuilder.php
deleted file mode 100644
index 60d26e3..0000000
--- a/repo/includes/rdf/ComplexValueRdfBuilder.php
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-
-namespace Wikibase\Rdf;
-
-use DataValues\DataValue;
-use Wikibase\DataModel\Snak\PropertyValueSnak;
-use Wikimedia\Purtle\RdfWriter;
-
-/**
- * RDF mapping for wikibase data values, using the fully expanded data 
representation.
- * This outputs expanded values (when appropriate) in addition to simple 
values.
- *
- * @since 0.5
- *
- * @licence GNU GPL v2+
- * @author Daniel Kinzler
- * @author Stas Malyshev
- */
-class ComplexValueRdfBuilder extends SimpleValueRdfBuilder {
-
-       /**
-        * @var DedupeBag
-        */
-       private $dedupeBag;
-
-       /**
-        * @var RdfWriter
-        */
-       private $valueWriter;
-
-       /**
-        * @param RdfVocabulary $vocabulary
-        * @param RdfWriter $valueWriter
-        */
-       public function __construct( RdfVocabulary $vocabulary, RdfWriter 
$valueWriter ) {
-               parent::__construct( $vocabulary );
-
-               $this->dedupeBag = new NullDedupeBag();
-               $this->valueWriter = $valueWriter;
-       }
-
-       /**
-        * @return DedupeBag
-        */
-       public function getDedupeBag() {
-               return $this->dedupeBag;
-       }
-
-       /**
-        * @param DedupeBag $dedupeBag
-        */
-       public function setDedupeBag( DedupeBag $dedupeBag ) {
-               $this->dedupeBag = $dedupeBag;
-       }
-
-       /**
-        * Adds the value of the given property to the RDF graph.
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param PropertyValueSnak $snak
-        */
-       public function addValue(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               PropertyValueSnak $snak
-       ) {
-               parent::addValue( $writer, $propertyValueNamespace, 
$propertyValueLName, $dataType, $snak );
-
-               $value = $snak->getDataValue();
-               switch ( $value->getType() ) {
-                       case 'time':
-                               $prefix = 'time';
-                               $fields = array(
-                                       'value' => 'dateTime',
-                                       'precision' => 'integer', // TODO: 
eventually use identifier here
-                                       'timezone' => 'integer',
-                                       'calendarModel' => 'url',
-                               );
-                               break;
-
-                       case 'quantity':
-                               $prefix = 'quantity';
-                               $fields = array(
-                                       'amount' => 'decimal',
-                                       'upperBound' => 'decimal',
-                                       'lowerBound' => 'decimal',
-                                       'unit' => 'url',
-                               );
-                               break;
-
-                       case 'globecoordinate':
-                               $prefix = 'geo';
-                               $fields = array(
-                                       'latitude' => 'decimal',
-                                       'longitude' => 'decimal',
-                                       'precision' => 'decimal',
-                                       'globe' => 'url',
-                               );
-                               break;
-
-                       default:
-                               return;
-               }
-
-               $valueLName = $this->addExpandedValue( $value, $prefix, $fields 
);
-               $writer->say( 
RdfVocabulary::$claimToValue[$propertyValueNamespace], $propertyValueLName )
-                       ->is( RdfVocabulary::NS_VALUE, $valueLName );
-       }
-
-       /**
-        * Created full data value
-        *
-        * @param DataValue $value
-        * @param string $prefix Prefix to use for predicate values
-        * @param array $props List of properties
-        *
-        * @return string the id of the value node, for use with the 
self::NS_VALUE namespace.
-        */
-       private function addExpandedValue( DataValue $value, $prefix, array 
$props ) {
-               $valueLName = $value->getHash();
-
-               if ( $this->dedupeBag->alreadySeen( $valueLName, 'V' ) !== 
false ) {
-                       return $valueLName;
-               }
-
-               $this->valueWriter->about( RdfVocabulary::NS_VALUE, $valueLName 
)
-                       ->a( RdfVocabulary::NS_ONTOLOGY, 
$this->vocabulary->getValueTypeName( $value ) );
-
-               foreach ( $props as $prop => $type ) {
-                       $propLName = $prefix . ucfirst( $prop );
-                       $getter = "get" . $prop;
-                       $data = $value->$getter();
-                       if ( !is_null( $data ) ) {
-                               $this->addValueToNode(
-                                       $this->valueWriter,
-                                       RdfVocabulary::NS_ONTOLOGY,
-                                       $propLName,
-                                       $type,
-                                       $data
-                               );
-                       }
-               }
-
-               return $valueLName;
-       }
-
-}
diff --git a/repo/includes/rdf/DispatchingValueSnakRdfBuilder.php 
b/repo/includes/rdf/DispatchingValueSnakRdfBuilder.php
index 8e858d6..ee6c03a 100644
--- a/repo/includes/rdf/DispatchingValueSnakRdfBuilder.php
+++ b/repo/includes/rdf/DispatchingValueSnakRdfBuilder.php
@@ -8,7 +8,7 @@
 
 /**
  * Dispatching implementation of ValueSnakRdfBuilder. This allows extensions 
to register
- * DataValueRdfBuilders for custom data types.
+ * ValueSnakRdfBuilders for custom data types.
  *
  * @since 0.5
  *
diff --git a/repo/includes/rdf/SimpleValueRdfBuilder.php 
b/repo/includes/rdf/SimpleValueRdfBuilder.php
deleted file mode 100644
index af6310c..0000000
--- a/repo/includes/rdf/SimpleValueRdfBuilder.php
+++ /dev/null
@@ -1,303 +0,0 @@
-<?php
-
-namespace Wikibase\Rdf;
-
-use DataValues\DecimalValue;
-use DataValues\Geo\Values\GlobeCoordinateValue;
-use DataValues\MonolingualTextValue;
-use DataValues\QuantityValue;
-use DataValues\StringValue;
-use DataValues\TimeValue;
-use Wikibase\DataModel\Entity\EntityIdValue;
-use Wikibase\DataModel\Snak\PropertyValueSnak;
-use Wikimedia\Purtle\RdfWriter;
-
-/**
- * RDF mapping for wikibase data values, using a simplified projection.
- *
- * @since 0.5
- *
- * @licence GNU GPL v2+
- * @author Daniel Kinzler
- * @author Stas Malyshev
- */
-class SimpleValueRdfBuilder implements ValueSnakRdfBuilder {
-
-       /**
-        * @var EntityMentionListener
-        */
-       private $mentionedEntityTracker;
-
-       /**
-        * @var DateTimeValueCleaner
-        */
-       private $dateCleaner;
-
-       /**
-        * @var RdfVocabulary
-        */
-       protected $vocabulary;
-
-       /**
-        * @param RdfVocabulary $vocabulary
-        */
-       public function __construct( RdfVocabulary $vocabulary ) {
-               $this->vocabulary = $vocabulary;
-
-               // TODO: if data is fixed to be always Gregorian, replace with 
DateTimeValueCleaner
-               $this->dateCleaner = new JulianDateTimeValueCleaner();
-               $this->mentionedEntityTracker = new NullEntityMentionListener();
-       }
-
-       /**
-        * @return EntityMentionListener
-        */
-       public function getEntityMentionListener() {
-               return $this->mentionedEntityTracker;
-       }
-
-       /**
-        * @param EntityMentionListener $mentionedEntityTracker
-        */
-       public function setEntityMentionListener( $mentionedEntityTracker ) {
-               $this->mentionedEntityTracker = $mentionedEntityTracker;
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param PropertyValueSnak $snak
-        */
-       public function addValue(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               PropertyValueSnak $snak
-       ) {
-               //FIXME: use a proper registry / dispatching builder
-               $value = $snak->getDataValue();
-               switch ( $value->getType() ) {
-                       //TODO: RdfWriter could support aliases -> instead of 
passing around $propertyNamespace
-                       //      and $propertyValueLName, we could define an 
alias for that and use e.g. '%property' to refer to them.
-                       case 'wikibase-entityid':
-                               $this->addStatementForWikibaseEntityid( 
$writer, $propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       case 'string':
-                               $this->addStatementForString( $writer, 
$propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       case 'monolingualtext':
-                               $this->addStatementForMonolingualtext( $writer, 
$propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       case 'time':
-                               $this->addStatementForTime( $writer, 
$propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       case 'quantity':
-                               $this->addStatementForQuantity( $writer, 
$propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       case 'globecoordinate':
-                               $this->addStatementForGlobecoordinate( $writer, 
$propertyValueNamespace, $propertyValueLName, $dataType, $value );
-                               break;
-                       default:
-                               wfLogWarning( __METHOD__ . ': Unsupported data 
value type: ' . $value->getType() );
-               }
-
-               // TODO: add special handling like in WDTK?
-               // 
https://github.com/Wikidata/Wikidata-Toolkit/blob/master/wdtk-rdf/src/main/java/org/
-               // wikidata/wdtk/rdf/extensions/SimpleIdExportExtension.java
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param EntityIdValue $value
-        */
-       private function addStatementForWikibaseEntityid(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               EntityIdValue $value
-       ) {
-               $entityId = $value->getValue()->getEntityId();
-               $entityLName = $this->vocabulary->getEntityLName( $entityId );
-               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->is( RdfVocabulary::NS_ENTITY, $entityLName );
-
-               $this->mentionedEntityTracker->entityReferenceMentioned( 
$entityId );
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param StringValue $value
-        */
-       private function addStatementForString(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               StringValue $value
-       ) {
-               switch ( $dataType ) {
-                       case 'url':
-                               $url = $value->getValue();
-                               break;
-                       case 'commonsMedia':
-                               $url = $this->vocabulary->getCommonsURI( 
$value->getValue() );
-                               break;
-                       default:
-                               $writer->say( $propertyValueNamespace, 
$propertyValueLName )
-                                       ->text( $value->getValue() );
-                               return;
-               }
-
-               $this->addValueToNode( $writer, $propertyValueNamespace, 
$propertyValueLName, 'url', $url );
-       }
-
-       /**
-        * Add value to a node
-        * This function does massaging needed for RDF data types.
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace
-        * @param string $propertyValueLName
-        * @param string $type
-        * @param mixed $value
-        */
-       protected function addValueToNode( RdfWriter $writer, 
$propertyValueNamespace, $propertyValueLName, $type, $value ) {
-               if ( $type === 'url' ) {
-                       if ( $value === "1" ) {
-                               // hack for units support, see 
https://phabricator.wikimedia.org/T105432
-                               $value = RdfVocabulary::ONE_ENTITY;
-                       }
-                       // Trims extra whitespace since we had a bug in 
wikidata where some URLs end up having it
-                       $writer->say( $propertyValueNamespace, 
$propertyValueLName )->is( trim( $value ) );
-               } elseif ( $type === 'dateTime' && $value instanceof TimeValue 
) {
-                       $writer->say( $propertyValueNamespace, 
$propertyValueLName );
-                       $this->sayDateLiteral( $writer, $value );
-               } elseif ( $type === 'decimal' ) {
-                       // TODO: handle precision here?
-                       if ( $value instanceof DecimalValue ) {
-                               $value = $value->getValue();
-                       }
-                       $writer->say( $propertyValueNamespace, 
$propertyValueLName )->value( $value, 'xsd', 'decimal' );
-               } else {
-                       if ( !is_scalar( $value ) ) {
-                               // somehow we got a weird value, better not 
risk it and bail
-                               $vtype = gettype( $value );
-                               wfLogWarning( "Bad value passed to 
addValueToNode for $propertyValueNamespace:$propertyValueLName: $vtype" );
-                               return;
-                       }
-                       $nsType = $type === null ? null : 'xsd';
-                       $writer->say( $propertyValueNamespace, 
$propertyValueLName )->value( $value, $nsType, $type );
-               }
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param MonolingualTextValue $value
-        */
-       private function addStatementForMonolingualtext(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               MonolingualTextValue $value
-       ) {
-               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->text( $value->getText(),
-                               $this->vocabulary->getCanonicalLanguageCode( 
$value->getLanguageCode() ) );
-       }
-
-       /**
-        * Produce literal that reperesent the date in RDF
-        * If we can convert it to xsd:dateTime, we'll do that.
-        * Otherwise, we leave it as string
-        *
-        * @param RdfWriter $writer The writer to receive the property value 
(must be primed to expect a predicate).
-        * @param TimeValue $value
-        */
-       private function sayDateLiteral( RdfWriter $writer, TimeValue $value ) {
-               $dateValue = $this->dateCleaner->getStandardValue( $value );
-               if ( !is_null( $dateValue ) ) {
-                       $writer->value( $dateValue, 'xsd', 'dateTime' );
-               } else {
-                       $writer->value( $value->getTime() );
-               }
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param TimeValue $value
-        */
-       private function addStatementForTime(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               TimeValue $value
-       ) {
-               $this->addValueToNode( $writer, $propertyValueNamespace, 
$propertyValueLName, 'dateTime', $value );
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param GlobeCoordinateValue $value
-        */
-       private function addStatementForGlobecoordinate(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               GlobeCoordinateValue $value
-       ) {
-               $point = "Point({$value->getLatitude()} 
{$value->getLongitude()})";
-               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->value( $point, RdfVocabulary::NS_GEO, "wktLiteral" );
-       }
-
-       /**
-        * Adds specific value
-        *
-        * @param RdfWriter $writer
-        * @param string $propertyValueNamespace Property value relation 
namespace
-        * @param string $propertyValueLName Property value relation name
-        * @param string $dataType Property data type
-        * @param QuantityValue $value
-        */
-       private function addStatementForQuantity(
-               RdfWriter $writer,
-               $propertyValueNamespace,
-               $propertyValueLName,
-               $dataType,
-               QuantityValue $value
-       ) {
-               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->value( $value->getAmount(), 'xsd', 'decimal' );
-       }
-
-}
diff --git a/repo/includes/rdf/Values/CommonsMediaRdfBuilder.php 
b/repo/includes/rdf/Values/CommonsMediaRdfBuilder.php
new file mode 100644
index 0000000..1886d9f
--- /dev/null
+++ b/repo/includes/rdf/Values/CommonsMediaRdfBuilder.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\DataValue;
+use Wikibase\Rdf\RdfVocabulary;
+
+/**
+ * RDF mapping for commonsMedia DataValues.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class CommonsMediaRdfBuilder extends ObjectUriRdfBuilder {
+
+       /**
+        * @var RdfVocabulary
+        */
+       private $vocabulary;
+
+       public function __construct( RdfVocabulary $vocabulary ) {
+               $this->vocabulary = $vocabulary;
+       }
+
+       /**
+        * @param DataValue $value
+        *
+        * @return string the object URI
+        */
+       protected function getValueUri( DataValue $value ) {
+               return $this->vocabulary->getCommonsURI( $value->getValue() );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/ComplexValueRdfHelper.php 
b/repo/includes/rdf/Values/ComplexValueRdfHelper.php
new file mode 100644
index 0000000..84180cd
--- /dev/null
+++ b/repo/includes/rdf/Values/ComplexValueRdfHelper.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\DataValue;
+use Wikibase\Rdf\DedupeBag;
+use Wikibase\Rdf\HashDedupeBag;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * Helper object for mapping DataValues to complex RDF structures (value 
nodes).
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class ComplexValueRdfHelper {
+
+       /**
+        * @var RdfVocabulary
+        */
+       private $vocabulary;
+
+       /**
+        * @var DedupeBag
+        */
+       private $dedupeBag;
+
+       /**
+        * @var RdfWriter
+        */
+       private $valueNodeWriter;
+
+       /**
+        * @param RdfVocabulary $vocabulary
+        * @param RdfWriter $valueNodeWriter
+        * @param DedupeBag|null $dedupeBag
+        */
+       public function __construct( RdfVocabulary $vocabulary, RdfWriter 
$valueNodeWriter, DedupeBag $dedupeBag = null ) {
+               $this->valueNodeWriter = $valueNodeWriter;
+               $this->vocabulary = $vocabulary;
+               $this->dedupeBag = $dedupeBag ?: new HashDedupeBag();
+       }
+
+       /**
+        * @return RdfWriter
+        */
+       public function getValueNodeWriter() {
+               return $this->valueNodeWriter;
+       }
+
+       /**
+        * Creates a value node for $value, and attaches it to the current 
subject of $writer.
+        * If a value node for $value was already created, null is returned. 
Otherwise, the
+        * value node's lname is returned, which should be used to generate 
detailed about the
+        * value into the writer returned by getValueNodeWriter().
+        *
+        * When this method returns a non-null lname, the current subject of 
the RdfWriter returned by
+        * getValueNodeWriter() will the be value node with that lname.
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type (unused, passed here for 
symmetry
+        *        with the signature of ValueSnakRdfBuilder::addValue).
+        * @param DataValue $value
+        *
+        * @return string|null The LName of the value node (in the 
RdfVocabulary::NS_VALUE namespace),
+        *  or null if the value node should not be processed (generally, 
because it already has
+        *  been processed).
+        */
+       public function attachValueNode(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               DataValue $value
+       ) {
+               $valueLName = $value->getHash();
+
+               $writer->say( 
RdfVocabulary::$claimToValue[$propertyValueNamespace], $propertyValueLName )
+                       ->is( RdfVocabulary::NS_VALUE, $valueLName );
+
+               if ( $this->dedupeBag->alreadySeen( $valueLName, 'V' ) !== 
false ) {
+                       return null;
+               }
+
+               $this->valueNodeWriter->about( RdfVocabulary::NS_VALUE, 
$valueLName )
+                       ->a( RdfVocabulary::NS_ONTOLOGY, 
$this->vocabulary->getValueTypeName( $value ) );
+
+               return $valueLName;
+       }
+
+}
diff --git a/repo/includes/rdf/Values/EntityIdRdfBuilder.php 
b/repo/includes/rdf/Values/EntityIdRdfBuilder.php
new file mode 100644
index 0000000..32cc90b
--- /dev/null
+++ b/repo/includes/rdf/Values/EntityIdRdfBuilder.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikibase\Rdf\EntityMentionListener;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for wikibase-entity DataValues.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class EntityIdRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * @var EntityMentionListener
+        */
+       private $mentionedEntityTracker;
+
+       /**
+        * @param RdfVocabulary $vocabulary
+        * @param EntityMentionListener $mentionedEntityTracker
+        */
+       public function __construct( RdfVocabulary $vocabulary, 
EntityMentionListener $mentionedEntityTracker ) {
+               $this->vocabulary = $vocabulary;
+
+               $this->mentionedEntityTracker = $mentionedEntityTracker;
+       }
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               $value = $snak->getDataValue();
+               $entityId = $value->getValue()->getEntityId();
+               $entityLName = $this->vocabulary->getEntityLName( $entityId );
+               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->is( RdfVocabulary::NS_ENTITY, $entityLName );
+
+               $this->mentionedEntityTracker->entityReferenceMentioned( 
$entityId );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/GlobeCoordinateRdfBuilder.php 
b/repo/includes/rdf/Values/GlobeCoordinateRdfBuilder.php
new file mode 100644
index 0000000..20bfb09
--- /dev/null
+++ b/repo/includes/rdf/Values/GlobeCoordinateRdfBuilder.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\Geo\Values\GlobeCoordinateValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for GlobeCoordinateValue.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class GlobeCoordinateRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * @var ComplexValueRdfHelper|null
+        */
+       private $complexValueHelper;
+
+       /**
+        * @param ComplexValueRdfHelper|null $complexValueHelper
+        */
+       public function __construct( ComplexValueRdfHelper $complexValueHelper 
= null ) {
+               $this->complexValueHelper = $complexValueHelper;
+       }
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               /** @var GlobeCoordinateValue $value */
+               $value = $snak->getDataValue();
+               $point = "Point({$value->getLatitude()} 
{$value->getLongitude()})";
+               $writer->say( $propertyValueNamespace, $propertyValueLName )
+                       ->value( $point, RdfVocabulary::NS_GEO, "wktLiteral" );
+
+               if ( $this->complexValueHelper !== null ) {
+                       $this->addValueNode( $writer, $propertyValueNamespace, 
$propertyValueLName, $dataType, $value );
+               }
+       }
+
+       /**
+        * Adds a value node representing all details of $value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param GlobeCoordinateValue $value
+        */
+       private function addValueNode(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               GlobeCoordinateValue $value
+       ) {
+               $valueLName = $this->complexValueHelper->attachValueNode(
+                       $writer,
+                       $propertyValueNamespace,
+                       $propertyValueLName,
+                       $dataType,
+                       $value
+               );
+
+               if ( $valueLName === null ) {
+                       // The value node is already present in the output, 
don't create it again!
+                       return;
+               }
+
+               $valueWriter = $this->complexValueHelper->getValueNodeWriter();
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'geoLatitude' )
+                       ->value( $value->getLatitude(), 'xsd', 'decimal' );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'geoLongitude' )
+                       ->value( $value->getLongitude(), 'xsd', 'decimal' );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'geoPrecision' )
+                       ->value( $value->getPrecision(), 'xsd', 'decimal' );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'geoGlobe' )
+                       ->is( trim( $value->getGlobe() ) );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/LiteralValueRdfBuilder.php 
b/repo/includes/rdf/Values/LiteralValueRdfBuilder.php
new file mode 100644
index 0000000..d2c3746
--- /dev/null
+++ b/repo/includes/rdf/Values/LiteralValueRdfBuilder.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\DataValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikimedia\Assert\Assert;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for DataValues that map to a literal.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class LiteralValueRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * @var string|null the base URI of the type
+        */
+       private $typeBase;
+
+       /**
+        * @var string|null the local name of the type
+        */
+       private $typeLocal;
+
+       /**
+        * @param string|null $typeBase
+        * @param string|null $typeLocal
+        */
+       public function __construct( $typeBase, $typeLocal ) {
+               Assert::parameterType( 'string|null', $typeBase, '$typeBase' );
+               Assert::parameterType( 'string|null', $typeLocal, '$typeLocal' 
);
+
+               $this->typeBase = $typeBase;
+               $this->typeLocal = $typeLocal;
+       }
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               $literalValue = $this->getLiteralValue( $snak->getDataValue() );
+               $nsType = $this->typeBase ?: ( $this->typeLocal === null ? null 
: 'xsd' );
+
+               $writer->say( $propertyValueNamespace, $propertyValueLName )
+                       ->value( $literalValue, $nsType, $this->typeLocal );
+       }
+
+       /**
+        * @param DataValue $value
+        *
+        * @return string
+        */
+       protected function getLiteralValue( DataValue $value ) {
+               return $value->getValue();
+       }
+
+}
diff --git a/repo/includes/rdf/Values/MonolingualTextRdfBuilder.php 
b/repo/includes/rdf/Values/MonolingualTextRdfBuilder.php
new file mode 100644
index 0000000..8cf0cbd
--- /dev/null
+++ b/repo/includes/rdf/Values/MonolingualTextRdfBuilder.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\MonolingualTextValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for MonolingualTextValues.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class MonolingualTextRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               /** @var MonolingualTextValue $value */
+               $value = $snak->getDataValue();
+               $writer->say( $propertyValueNamespace, $propertyValueLName )
+                       ->text( $value->getText(), $value->getLanguageCode() );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/ObjectUriRdfBuilder.php 
b/repo/includes/rdf/Values/ObjectUriRdfBuilder.php
new file mode 100644
index 0000000..4585f87
--- /dev/null
+++ b/repo/includes/rdf/Values/ObjectUriRdfBuilder.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\DataValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for DataValues that map to a resource (object) URI.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class ObjectUriRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               $uri = $this->getValueUri( $snak->getDataValue() );
+
+               $writer->say( $propertyValueNamespace, $propertyValueLName 
)->is( $uri );
+       }
+
+       /**
+        * @param DataValue $value
+        *
+        * @return string the object URI
+        */
+       protected function getValueUri( DataValue $value ) {
+               return trim( strval( $value->getValue() ) );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/QuantityRdfBuilder.php 
b/repo/includes/rdf/Values/QuantityRdfBuilder.php
new file mode 100644
index 0000000..daa28c1
--- /dev/null
+++ b/repo/includes/rdf/Values/QuantityRdfBuilder.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\DataValue;
+use DataValues\QuantityValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for QuantityValue.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class QuantityRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * @var ComplexValueRdfHelper|null
+        */
+       private $complexValueHelper;
+
+       /**
+        * @param ComplexValueRdfHelper|null $complexValueHelper
+        */
+       public function __construct( ComplexValueRdfHelper $complexValueHelper 
= null ) {
+               $this->complexValueHelper = $complexValueHelper;
+       }
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               /** @var QuantityValue $value */
+               $value = $snak->getDataValue();
+               $writer->say( $propertyValueNamespace, $propertyValueLName )
+                       ->value( $value->getAmount(), 'xsd', 'decimal' );
+               //FIXME: this is meaningless without a unit identifier!
+
+               if ( $this->complexValueHelper !== null ) {
+                       $this->addValueNode( $writer, $propertyValueNamespace, 
$propertyValueLName, $dataType, $value );
+               }
+       }
+
+       /**
+        * Adds a value node representing all details of $value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param QuantityValue $value
+        */
+       private function addValueNode(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               QuantityValue $value
+       ) {
+               $valueLName = $this->complexValueHelper->attachValueNode(
+                       $writer,
+                       $propertyValueNamespace,
+                       $propertyValueLName,
+                       $dataType,
+                       $value
+               );
+
+               if ( $valueLName === null ) {
+                       // The value node is already present in the output, 
don't create it again!
+                       return;
+               }
+
+               $valueWriter = $this->complexValueHelper->getValueNodeWriter();
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'quantityAmount' 
)
+                       ->value( $value->getAmount(), 'xsd', 'decimal' );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 
'quantityUpperBound' )
+                       ->value( $value->getUpperBound(), 'xsd', 'decimal' );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 
'quantityLowerBound' )
+                       ->value( $value->getLowerBound(), 'xsd', 'decimal' );
+
+               $unitUri = trim( $value->getUnit() );
+
+               if ( $unitUri === '1' ) {
+                       $unitUri = RdfVocabulary::ONE_ENTITY;
+               }
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'quantityUnit' )
+                       ->is( $unitUri );
+       }
+
+}
diff --git a/repo/includes/rdf/Values/TimeRdfBuilder.php 
b/repo/includes/rdf/Values/TimeRdfBuilder.php
new file mode 100644
index 0000000..da61476
--- /dev/null
+++ b/repo/includes/rdf/Values/TimeRdfBuilder.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Wikibase\Rdf\Values;
+
+use DataValues\TimeValue;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikibase\Rdf\DateTimeValueCleaner;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * RDF mapping for TimeValues.
+ *
+ * @since 0.5
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ * @author Stas Malyshev
+ */
+class TimeRdfBuilder implements ValueSnakRdfBuilder {
+
+       /**
+        * @var DateTimeValueCleaner
+        */
+       private $dateCleaner;
+
+       /**
+        * @var ComplexValueRdfHelper|null
+        */
+       private $complexValueHelper;
+
+       /**
+        * @param DateTimeValueCleaner $dateCleaner
+        * @param ComplexValueRdfHelper|null $complexValueHelper
+        */
+       public function __construct(
+               DateTimeValueCleaner $dateCleaner,
+               ComplexValueRdfHelper $complexValueHelper = null
+       ) {
+               $this->dateCleaner = $dateCleaner;
+               $this->complexValueHelper = $complexValueHelper;
+       }
+
+       /**
+        * Adds specific value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param PropertyValueSnak $snak
+        */
+       public function addValue(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               PropertyValueSnak $snak
+       ) {
+               $writer->say( $propertyValueNamespace, $propertyValueLName );
+
+               /** @var TimeValue $value */
+               $value = $snak->getDataValue();
+               $this->sayDateLiteral( $writer, $value );
+
+               if ( $this->complexValueHelper !== null ) {
+                       $this->addValueNode( $writer, $propertyValueNamespace, 
$propertyValueLName, $dataType, $value );
+               }
+       }
+
+       private function sayDateLiteral( RdfWriter $writer, TimeValue $value ) {
+               $dateValue = $this->dateCleaner->getStandardValue( $value );
+               if ( !is_null( $dateValue ) ) {
+                       // XXX: type should perhaps depend on precision.
+                       $writer->value( $dateValue, 'xsd', 'dateTime' );
+               } else {
+                       $writer->value( $value->getTime() );
+               }
+       }
+
+       /**
+        * Adds a value node representing all details of $value
+        *
+        * @param RdfWriter $writer
+        * @param string $propertyValueNamespace Property value relation 
namespace
+        * @param string $propertyValueLName Property value relation name
+        * @param string $dataType Property data type
+        * @param TimeValue $value
+        */
+       private function addValueNode(
+               RdfWriter $writer,
+               $propertyValueNamespace,
+               $propertyValueLName,
+               $dataType,
+               TimeValue $value
+       ) {
+               $valueLName = $this->complexValueHelper->attachValueNode(
+                       $writer,
+                       $propertyValueNamespace,
+                       $propertyValueLName,
+                       $dataType,
+                       $value
+               );
+
+               if ( $valueLName === null ) {
+                       // The value node is already present in the output, 
don't create it again!
+                       return;
+               }
+
+               $valueWriter = $this->complexValueHelper->getValueNodeWriter();
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'timeValue' );
+               $this->sayDateLiteral( $valueWriter, $value );
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'timePrecision' )
+                       ->value( $value->getPrecision(), 'xsd', 'integer' ); 
//TODO: use identifiers
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 'timeTimezone' )
+                       ->value( $value->getTimezone(), 'xsd', 'integer' ); 
//XXX: underspecified
+
+               $valueWriter->say( RdfVocabulary::NS_ONTOLOGY, 
'timeCalendarModel' )
+                       ->is( trim( $value->getCalendarModel() ) );
+       }
+
+}
diff --git a/repo/tests/phpunit/data/rdf/dump_empty.nt 
b/repo/tests/phpunit/data/rdf/RdfDumpGenerator/empty.nt
similarity index 100%
rename from repo/tests/phpunit/data/rdf/dump_empty.nt
rename to repo/tests/phpunit/data/rdf/RdfDumpGenerator/empty.nt
diff --git a/repo/tests/phpunit/data/rdf/dump_entities.nt 
b/repo/tests/phpunit/data/rdf/RdfDumpGenerator/entities.nt
similarity index 100%
rename from repo/tests/phpunit/data/rdf/dump_entities.nt
rename to repo/tests/phpunit/data/rdf/RdfDumpGenerator/entities.nt
diff --git a/repo/tests/phpunit/data/rdf/dump_redirect.nt 
b/repo/tests/phpunit/data/rdf/RdfDumpGenerator/redirect.nt
similarity index 100%
rename from repo/tests/phpunit/data/rdf/dump_redirect.nt
rename to repo/tests/phpunit/data/rdf/RdfDumpGenerator/redirect.nt
diff --git a/repo/tests/phpunit/data/rdf/dump_refs.nt 
b/repo/tests/phpunit/data/rdf/RdfDumpGenerator/refs.nt
similarity index 97%
rename from repo/tests/phpunit/data/rdf/dump_refs.nt
rename to repo/tests/phpunit/data/rdf/RdfDumpGenerator/refs.nt
index 13176f7..f8de459 100644
--- a/repo/tests/phpunit/data/rdf/dump_refs.nt
+++ b/repo/tests/phpunit/data/rdf/RdfDumpGenerator/refs.nt
@@ -10,7 +10,7 @@
 <http://acme.test/Q9> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> 
<http://wikiba.se/ontology-beta#Item> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P2> <http://acme.test/Q42> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P2> <http://acme.test/Q666> .
-<http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P3> "Universe.svg" .
+<http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P3> 
<http://commons.wikimedia.org/wiki/Special:FilePath/Universe.svg> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> 
<http://acme.test/prop/novalue/P3> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/value/P4> 
<http://acme.test/value/aad6b70bccf9875ba61d31c767b7f652> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P4> "Point(12.345 
67.89)"^^<http://www.opengis.net/ont/geosparql#wktLiteral> .
@@ -22,7 +22,7 @@
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P7> "simplestring" .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/value/P8> 
<http://acme.test/value/ba4fa68a0979e663277b08d93f93705c> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P8> 
"-0199-01-01T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
-<http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P9> "http://url.acme.test/"; .
+<http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://acme.test/prop/reference/P9> <http://url.acme.test/> .
 <http://acme.test/reference/569a639de6dc67beb02644abfcf55534cb2f51ce> 
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> 
<http://wikiba.se/ontology-beta#Reference> .
 <http://acme.test/statement/TEST-References-2-Dup> 
<http://acme.test/prop/statement/P7> "string2" .
 <http://acme.test/statement/TEST-References-2-Dup> 
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> 
<http://wikiba.se/ontology-beta#Statement> .
diff --git a/repo/tests/phpunit/includes/Dumpers/RdfDumpGeneratorTest.php 
b/repo/tests/phpunit/includes/Dumpers/RdfDumpGeneratorTest.php
index a7e9348..0a1b340 100644
--- a/repo/tests/phpunit/includes/Dumpers/RdfDumpGeneratorTest.php
+++ b/repo/tests/phpunit/includes/Dumpers/RdfDumpGeneratorTest.php
@@ -17,6 +17,7 @@
 use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
 use Wikibase\Repo\WikibaseRepo;
 use Wikibase\Test\Rdf\RdfBuilderTest;
+use Wikibase\Test\Rdf\RdfBuilderTestData;
 
 /**
  * @covers Wikibase\Dumpers\RdfDumpGenerator
@@ -61,6 +62,13 @@
                return $list;
        }
 
+       private function getTestData() {
+               return new RdfBuilderTestData(
+                       __DIR__ . '/../../data/rdf',
+                       __DIR__ . '/../../data/rdf/RdfDumpGenerator'
+               );
+       }
+
        /**
         * @param Entity[] $entities
         * @param EntityId[] $redirects
@@ -73,11 +81,8 @@
 
                $entityLookup = $this->getMock( 
'Wikibase\DataModel\Services\Lookup\EntityLookup' );
                $entityRevisionLookup = $this->getMock( 
'Wikibase\Lib\Store\EntityRevisionLookup' );
-               $dataTypeLookup = $this->getMock( 
'Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup' );
 
-               $dataTypeLookup->expects( $this->any() )
-                       ->method( 'getDataTypeIdForProperty' )
-                       ->will( $this->returnValue( 'string' ) );
+               $dataTypeLookup = $this->getTestData()->getMockRepository();
 
                $entityLookup->expects( $this->any() )
                        ->method( 'getEntity' )
@@ -144,7 +149,7 @@
         * @return string[]
         */
        public function normalizeData( $data ) {
-               $dataSplit = explode( "\n", $data );
+               $dataSplit = explode( "\n", trim( $data ) );
                sort( $dataSplit );
                $dataSplit = array_map( 'trim', $dataSplit );
                return $dataSplit;
@@ -158,11 +163,7 @@
         * @return string[]
         */
        public function getSerializedData( $testName ) {
-               $filename = __DIR__ . "/../../data/rdf/dump_$testName.nt";
-               if ( !file_exists( $filename ) ) {
-                       return array();
-               }
-               return $this->normalizeData( file_get_contents( $filename ) );
+               return $this->getTestData()->getNTriples( $testName );
        }
 
        /**
@@ -181,7 +182,7 @@
                $dumper->generateDump( $pager );
                $dump = ob_get_clean();
                $dump = $this->normalizeData( $dump );
-               $this->assertEquals( $this->getSerializedData( $dumpname ), 
$dump );
+               $this->assertTriplesEqual( $this->getSerializedData( $dumpname 
), $dump );
        }
 
        public function loadDataProvider() {
@@ -213,7 +214,23 @@
                $dumper->generateDump( $pager );
                $dump = ob_get_clean();
                $dump = $this->normalizeData( $dump );
-               $this->assertEquals( $this->getSerializedData( $dumpname ), 
$dump );
+               $this->assertTriplesEqual( $this->getSerializedData( $dumpname 
), $dump );
+       }
+
+       private function assertTriplesEqual( array $expectedTriples, array 
$actualTripels, $message = '' ) {
+               sort( $expectedTriples );
+               sort( $actualTripels );
+
+               // Note: comparing $expected and $actual directly would show 
triples
+               // that are present in both but shifted in position. That makes 
the output
+               // hard to read. Calculating the $missing and $extra sets helps.
+               $extra = array_diff( $actualTripels, $expectedTriples );
+               $missing = array_diff( $expectedTriples, $actualTripels );
+
+               // Cute: $missing and $extra can be equal only if they are 
empty.
+               // Comparing them here directly looks a bit odd in code, but 
produces meaningful
+               // output, especially if the input was sorted.
+               $this->assertEquals( $missing, $extra, $message );
        }
 
 }
diff --git 
a/repo/tests/phpunit/includes/LinkedData/EntityDataSerializationServiceTest.php 
b/repo/tests/phpunit/includes/LinkedData/EntityDataSerializationServiceTest.php
index d83c815..d5858ef 100644
--- 
a/repo/tests/phpunit/includes/LinkedData/EntityDataSerializationServiceTest.php
+++ 
b/repo/tests/phpunit/includes/LinkedData/EntityDataSerializationServiceTest.php
@@ -84,7 +84,7 @@
                $dataTypeLookup = $this->getMock( 
'Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup' );
                $dataTypeLookup->expects( $this->any() )
                        ->method( 'getDataTypeIdForProperty' )
-                       ->will( $this->returnValue( 'string' ) );
+                       ->will( $this->returnValue( 'wikibase-item' ) );
 
                $titleLookup = $this->getMock( 
'Wikibase\Lib\Store\EntityTitleLookup' );
                $titleLookup->expects( $this->any() )
@@ -92,10 +92,6 @@
                        ->will( $this->returnCallback( function( EntityId $id ) 
{
                                return Title::newFromText( $id->getEntityType() 
. ':' . $id->getSerialization() );
                        } ) );
-               $dataTypeLookup = $this->getMock( 
'Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup' );
-               $dataTypeLookup->expects( $this->any() )
-                       ->method( 'getDataTypeIdForProperty' )
-                       ->will( $this->returnValue( 'string' ) );
 
                $serializerFactory = new SerializerFactory(
                        new DataValueSerializer(),
diff --git a/repo/tests/phpunit/includes/rdf/ComplexValueRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/ComplexValueRdfBuilderTest.php
index d24682b..2fa2ce7 100644
--- a/repo/tests/phpunit/includes/rdf/ComplexValueRdfBuilderTest.php
+++ b/repo/tests/phpunit/includes/rdf/ComplexValueRdfBuilderTest.php
@@ -15,15 +15,18 @@
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
 use Wikibase\DataModel\Snak\PropertyValueSnak;
-use Wikibase\Rdf\ComplexValueRdfBuilder;
+use Wikibase\Rdf\NullDedupeBag;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
 use Wikibase\Rdf\DedupeBag;
 use Wikibase\Rdf\HashDedupeBag;
-use Wikibase\Rdf\NullDedupeBag;
 use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Repo\WikibaseRepo;
 use Wikimedia\Purtle\RdfWriter;
 
 /**
- * @covers Wikibase\Rdf\ComplexValueRdfBuilder
+ * Test for integration of ValueSnakRdfBuilderFactory, 
DispatchingValueSnakRdfBuilder, and various
+ * handlers for different data types. Should allow confident transition from 
the old
+ * ComplexValueRdfBuilder.
  *
  * @group Wikibase
  * @group WikibaseRepo
@@ -60,7 +63,7 @@
         * @param EntityId[] &$mentioned receives the IDs of any mentioned 
entities.
         * @param DedupeBag|null $bag A list of value hashes that should be 
considered "already seen".
         *
-        * @return ComplexValueRdfBuilder
+        * @return ValueSnakRdfBuilder
         */
        private function newBuilder( array &$mentioned = array(), DedupeBag 
$bag = null ) {
                $mentionTracker = $this->getMock( 
'Wikibase\Rdf\EntityMentionListener' );
@@ -73,13 +76,14 @@
 
                $valueWriter = $this->getTestData()->getNTriplesWriter();
 
-               $builder = new ComplexValueRdfBuilder(
+               $valueSnakRdfBuilderFactory = 
WikibaseRepo::getDefaultInstance()->getValueSnakRdfBuilderFactory();
+
+               $builder = 
$valueSnakRdfBuilderFactory->getComplexValueSnakRdfBuilder(
                        $this->getTestData()->getVocabulary(),
                        $valueWriter,
-                       $this->getTestData()->getMockRepository()
+                       $mentionTracker,
+                       $bag ?: new NullDedupeBag()
                );
-               $builder->setEntityMentionListener( $mentionTracker );
-               $builder->setDedupeBag( $bag ?: new NullDedupeBag() );
 
                // HACK: glue on the value writer as a public field, so we can 
evaluate it later.
                $builder->test_value_writer = $valueWriter;
@@ -152,7 +156,7 @@
                        ),
                        'globecoordinate' => array(
                                new PropertyId( 'P4' ),
-                               'globecoordinate',
+                               'globe-coordinate',
                                new GlobeCoordinateValue(
                                        new LatLongValue( 12.25, -45.5 ),
                                        0.025,
diff --git a/repo/tests/phpunit/includes/rdf/SimpleValueRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/SimpleValueRdfBuilderTest.php
index 12583ce..ba0aa29 100644
--- a/repo/tests/phpunit/includes/rdf/SimpleValueRdfBuilderTest.php
+++ b/repo/tests/phpunit/includes/rdf/SimpleValueRdfBuilderTest.php
@@ -15,12 +15,16 @@
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
 use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\Rdf\ValueSnakRdfBuilder;
+use Wikibase\Rdf\HashDedupeBag;
 use Wikibase\Rdf\RdfVocabulary;
-use Wikibase\Rdf\SimpleValueRdfBuilder;
+use Wikibase\Repo\WikibaseRepo;
 use Wikimedia\Purtle\RdfWriter;
 
 /**
- * @covers Wikibase\Rdf\SimpleValueRdfBuilder
+ * Test for integration of ValueSnakRdfBuilderFactory, 
DispatchingValueSnakRdfBuilder, and various
+ * handlers for different data types. Should allow confident transition from 
the old
+ * SimpleValueRdfBuilder.
  *
  * @group Wikibase
  * @group WikibaseRepo
@@ -56,7 +60,7 @@
        /**
         * @param EntityId[] &$mentioned receives the IDs of any mentioned 
entities.
         *
-        * @return SimpleValueRdfBuilder
+        * @return ValueSnakRdfBuilder
         */
        private function newBuilder( array &$mentioned = array() ) {
                $mentionTracker = $this->getMock( 
'Wikibase\Rdf\EntityMentionListener' );
@@ -67,10 +71,14 @@
                                $mentioned[$key] = $id;
                        } ) );
 
-               $vocabulary = $this->getTestData()->getVocabulary();
+               $valueSnakRdfBuilderFactory = 
WikibaseRepo::getDefaultInstance()->getValueSnakRdfBuilderFactory();
 
-               $builder = new SimpleValueRdfBuilder( $vocabulary, 
$this->getTestData()->getMockRepository() );
-               $builder->setEntityMentionListener( $mentionTracker );
+               $builder = 
$valueSnakRdfBuilderFactory->getSimpleValueSnakRdfBuilder(
+                       $this->getTestData()->getVocabulary(),
+                       $this->getTestData()->getNTriplesWriter(),
+                       $mentionTracker,
+                       new HashDedupeBag()
+               );
 
                return $builder;
        }
@@ -124,7 +132,7 @@
                        ),
                        'globecoordinate' => array(
                                new PropertyId( 'P4' ),
-                               'globecoordinate',
+                               'globe-coordinate',
                                new GlobeCoordinateValue(
                                        new LatLongValue( 12.25, -45.5 ),
                                        0.025,
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/CommonsMediaRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/CommonsMediaRdfBuilderTest.php
new file mode 100644
index 0000000..fba7ffc
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/CommonsMediaRdfBuilderTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\CommonsMediaRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\CommonsMediaRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class CommonsMediaRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function testAddValue() {
+               $vocab = new RdfVocabulary( 'http://test/item/', 
'http://test/data/' );
+               $builder = new CommonsMediaRdfBuilder( $vocab );
+
+               $writer = new NTriplesRdfWriter();
+               $writer->prefix( 'www', "http://www/"; );
+               $writer->prefix( 'acme', "http://acme/"; );
+
+               $writer->start();
+               $writer->about( 'www', 'Q1' );
+
+               $snak = new PropertyValueSnak(
+                       new PropertyId( 'P1' ),
+                       new StringValue( 'Bunny.jpg' )
+               );
+
+               $builder->addValue( $writer, 'acme', 'testing', 'DUMMY', $snak 
);
+
+               $expected = array( '<http://www/Q1> <http://acme/testing> 
<http://commons.wikimedia.org/wiki/Special:FilePath/Bunny.jpg> .' );
+               $triples = explode( "\n", trim( $writer->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/ComplexValueRdfHelperTest.php 
b/repo/tests/phpunit/includes/rdf/Values/ComplexValueRdfHelperTest.php
new file mode 100644
index 0000000..83ef820
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/ComplexValueRdfHelperTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\HashDedupeBag;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\ComplexValueRdfHelper;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\ComplexValueRdfHelper
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class ComplexValueRdfHelperTest extends \PHPUnit_Framework_TestCase {
+
+       public function testAttachValueNode() {
+               $vocab = new RdfVocabulary( 'http://acme.com/item/', 
'http://acme.com/data/' );
+
+               $snakWriter = new NTriplesRdfWriter();
+               $snakWriter->prefix( 'www', "http://www/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_VALUE, 
"http://acme/statement/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_VALUE, 
"http://acme/value/"; );
+
+               $valueWriter = new NTriplesRdfWriter();
+               $valueWriter->prefix( RdfVocabulary::NS_VALUE, 
"http://acme/value/"; );
+               $valueWriter->prefix( RdfVocabulary::NS_ONTOLOGY, 
"http://acme/onto/"; );
+
+               $helper = new ComplexValueRdfHelper( $vocab, $valueWriter, new 
HashDedupeBag() );
+
+               // check we get the correct value writer
+               $this->assertSame( $valueWriter, $helper->getValueNodeWriter() 
);
+
+               $snakWriter->start();
+               $snakWriter->about( 'www', 'Q1' );
+
+               $valueWriter->start();
+
+               // attach a value node
+               $value = new StringValue( 
'http://en.wikipedia.org/wiki/Wikidata' );
+               $lvalue = $helper->attachValueNode( $snakWriter, 
RdfVocabulary::NSP_CLAIM_STATEMENT, 'testing', 'DUMMY', $value );
+               $this->assertEquals( 'e93b68fef814eb52e813bb72e6867432', 
$lvalue );
+
+               // do it again, tests dedupe
+               $snakWriter->about( 'www', 'Q2' );
+               $lvalue = $helper->attachValueNode( $snakWriter, 
RdfVocabulary::NSP_CLAIM_STATEMENT, 'testing', 'DUMMY', $value );
+               $this->assertNull( $lvalue, 'lvalue produced by adding a value 
a second time should be null' );
+
+               // check the triples written to the snak writer
+               $expected = array(
+                       '<http://www/Q1> <http://acme/statement/value/testing> 
<http://acme/value/e93b68fef814eb52e813bb72e6867432> .',
+                       '<http://www/Q2> <http://acme/statement/value/testing> 
<http://acme/value/e93b68fef814eb52e813bb72e6867432> .'
+               );
+
+               $triples = explode( "\n", trim( $snakWriter->drain() ) );
+               $this->assertEquals( $expected, $triples );
+
+               // check the triples written to the value writer
+               $expected = array( 
'<http://acme/value/e93b68fef814eb52e813bb72e6867432> '
+                       . '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> '
+                       . '<http://acme/onto/StringValue> .' );
+               $triples = explode( "\n", trim( $valueWriter->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/rdf/Values/EntityIdRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/EntityIdRdfBuilderTest.php
new file mode 100644
index 0000000..c2af600
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/EntityIdRdfBuilderTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\NullEntityMentionListener;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\EntityIdRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\EntityIdRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class EntityIdRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function testAddValue() {
+               $vocab = new RdfVocabulary( 'http://test/item/', 
'http://test/data/' );
+               $builder = new EntityIdRdfBuilder( $vocab, new 
NullEntityMentionListener() );
+
+               $writer = new NTriplesRdfWriter();
+               $writer->prefix( 'www', "http://www/"; );
+               $writer->prefix( 'acme', "http://acme/"; );
+               $writer->prefix( RdfVocabulary::NS_ENTITY, 'http://test/item/' 
);
+
+               $writer->start();
+               $writer->about( 'www', 'Q1' );
+
+               $snak = new PropertyValueSnak(
+                       new PropertyId( 'P1' ),
+                       new EntityIdValue( new ItemId( 'Q23' ) )
+               );
+
+               $builder->addValue( $writer, 'acme', 'testing', 'DUMMY', $snak 
);
+
+               $expected = array( '<http://www/Q1> <http://acme/testing> 
<http://test/item/Q23> .' );
+               $triples = explode( "\n", trim( $writer->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/GlobeCoordinateRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/GlobeCoordinateRdfBuilderTest.php
new file mode 100644
index 0000000..f9a6449
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/GlobeCoordinateRdfBuilderTest.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\Geo\Values\LatLongValue;
+use DataValues\GlobeCoordinateValue;
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\HashDedupeBag;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\ComplexValueRdfHelper;
+use Wikibase\Rdf\Values\GlobeCoordinateRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\GlobeCoordinateRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class GlobeCoordinateRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function provideAddValue() {
+               $value = new GlobeCoordinateValue(
+                       new LatLongValue( 12.25, -45.5 ),
+                       0.025,
+                       'https://www.wikidata.org/entity/Q2'
+               );
+
+               $snak = new PropertyValueSnak( new PropertyId( 'P7' ), $value );
+
+               return array(
+                       'simple' => array(
+                               $snak,
+                               false,
+                               array(
+                                       '<http://www/Q1> 
<http://acme/statement/P7> "Point(12.25 -45.5)"^^<http://acme/geo/wktLiteral> 
.',
+                               )
+                       ),
+                       'complex' => array(
+                               $snak,
+                               true,
+                               array(
+                                       '<http://www/Q1> '
+                                               . '<http://acme/statement/P7> '
+                                               . '"Point(12.25 
-45.5)"^^<http://acme/geo/wktLiteral> .',
+                                       '<http://www/Q1> '
+                                               . 
'<http://acme/statement/value/P7> '
+                                               . 
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> .',
+                                       
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> '
+                                               . 
'<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> '
+                                               . 
'<http://acme/onto/GlobecoordinateValue> .',
+                                       
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> '
+                                               . 
'<http://acme/onto/geoLatitude> '
+                                               . 
'"12.25"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> '
+                                               . 
'<http://acme/onto/geoLongitude> '
+                                               . 
'"-45.5"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> '
+                                               . 
'<http://acme/onto/geoPrecision> '
+                                               . 
'"0.025"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/7901049a90a3b6a6cbbae50dc76c2da9> '
+                                               . '<http://acme/onto/geoGlobe> '
+                                               . 
'<https://www.wikidata.org/entity/Q2> .',
+                               )
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider provideAddValue
+        */
+       public function testAddValue( PropertyValueSnak $snak, $complex, array 
$expected ) {
+               $vocab = new RdfVocabulary( 'http://acme.com/item/', 
'http://acme.com/data/' );
+
+               $snakWriter = new NTriplesRdfWriter();
+               $snakWriter->prefix( 'www', "http://www/"; );
+               $snakWriter->prefix( 'acme', "http://acme/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_VALUE, 
"http://acme/statement/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_STATEMENT, 
"http://acme/statement/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_VALUE, 
"http://acme/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_ONTOLOGY, 
"http://acme/onto/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_GEO, "http://acme/geo/"; 
);
+
+               if ( $complex ) {
+                       $valueWriter = $snakWriter->sub();
+                       $helper = new ComplexValueRdfHelper( $vocab, 
$valueWriter, new HashDedupeBag() );
+               } else {
+                       $helper = null;
+               }
+
+               $builder = new GlobeCoordinateRdfBuilder( $helper );
+
+               $snakWriter->start();
+               $snakWriter->about( 'www', 'Q1' );
+
+               $builder->addValue(
+                       $snakWriter,
+                       RdfVocabulary::NSP_CLAIM_STATEMENT,
+                       $vocab->getEntityLName( $snak->getPropertyid() ),
+                       'DUMMY',
+                       $snak
+               );
+
+               $triples = trim( $snakWriter->drain() );
+               $this->assertEquals( join( "\n", $expected ), $triples );
+       }
+
+}
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/LiteralValueRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/LiteralValueRdfBuilderTest.php
new file mode 100644
index 0000000..3b200f3
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/LiteralValueRdfBuilderTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\Values\LiteralValueRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\LiteralValueRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class LiteralValueRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function provideAddValue() {
+               $p11 = new PropertyId( 'P11' );
+               $stringSnak = new PropertyValueSnak( $p11, new StringValue( 
'Hello World' ) );
+               $numberSnak = new PropertyValueSnak( $p11, new StringValue( 
'15' ) );
+
+               return array(
+                       'plain string' => array(
+                               null, null,
+                               $stringSnak,
+                               array( '<http://www/Q1> <http://acme/testing> 
"Hello World" .' )
+                       ),
+                       'xsd decimal' => array(
+                               null, 'decimal',
+                               $numberSnak,
+                               array( '<http://www/Q1> <http://acme/testing> 
"15"^^<http://www.w3.org/2001/XMLSchema#decimal> .' )
+                       ),
+                       'wd id' => array(
+                               'xx', 'id',
+                               $stringSnak,
+                               array( '<http://www/Q1> <http://acme/testing> 
"Hello World"^^<http://xx/id> .' )
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider provideAddValue
+        */
+       public function testAddValue(
+               $typeBase, $typeLocal,
+               PropertyValueSnak $snak,
+               array $expected
+       ) {
+               $builder = new LiteralValueRdfBuilder( $typeBase, $typeLocal );
+
+               $writer = new NTriplesRdfWriter();
+               $writer->prefix( 'www', "http://www/"; );
+               $writer->prefix( 'acme', "http://acme/"; );
+               $writer->prefix( $typeBase, "http://$typeBase/"; );
+
+               $writer->start();
+               $writer->about( 'www', 'Q1' );
+
+               $builder->addValue( $writer, 'acme', 'testing', 'DUMMY', $snak 
);
+
+               $triples = explode( "\n", trim( $writer->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/MonolingualTextRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/MonolingualTextRdfBuilderTest.php
new file mode 100644
index 0000000..886b2c4
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/MonolingualTextRdfBuilderTest.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\MonolingualTextValue;
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\Values\MonolingualTextRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\MonolingualTextRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class MonolingualTextRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function testAddValue() {
+               $builder = new MonolingualTextRdfBuilder();
+               $writer = new NTriplesRdfWriter();
+               $writer->prefix( 'www', "http://www/"; );
+               $writer->prefix( 'acme', "http://acme/"; );
+
+               $writer->start();
+               $writer->about( 'www', 'Q1' );
+
+               $snak = new PropertyValueSnak( new PropertyId( 'P1' ), new 
MonolingualTextValue( 'de', 'Hallo Welt' ) );
+               $builder->addValue( $writer, 'acme', 'testing', 'DUMMY', $snak 
);
+
+               $expected = array( '<http://www/Q1> <http://acme/testing> 
"Hallo Welt"@de .' );
+               $triples = explode( "\n", trim( $writer->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git 
a/repo/tests/phpunit/includes/rdf/Values/ObjectValueRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/ObjectValueRdfBuilderTest.php
new file mode 100644
index 0000000..c19c661
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/ObjectValueRdfBuilderTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\Values\ObjectUriRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\ObjectValueRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class ObjectValueRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function testAddValue() {
+               $builder = new ObjectUriRdfBuilder();
+               $writer = new NTriplesRdfWriter();
+               $writer->prefix( 'www', "http://www/"; );
+               $writer->prefix( 'acme', "http://acme/"; );
+
+               $writer->start();
+               $writer->about( 'www', 'Q1' );
+
+               $snak = new PropertyValueSnak(
+                       new PropertyId( 'P1' ),
+                       new StringValue( 
'http://en.wikipedia.org/wiki/Wikidata' )
+               );
+
+               $builder->addValue( $writer, 'acme', 'testing', 'DUMMY', $snak 
);
+
+               $expected = array( '<http://www/Q1> <http://acme/testing> 
<http://en.wikipedia.org/wiki/Wikidata> .' );
+               $triples = explode( "\n", trim( $writer->drain() ) );
+               $this->assertEquals( $expected, $triples );
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/rdf/Values/QuantityRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/QuantityRdfBuilderTest.php
new file mode 100644
index 0000000..495f0e9
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/QuantityRdfBuilderTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\QuantityValue;
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\HashDedupeBag;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\ComplexValueRdfHelper;
+use Wikibase\Rdf\Values\QuantityRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\QuantityRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class QuantityRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function provideAddValue() {
+               $value = QuantityValue::newFromNumber( '+23.5', '1', '+23.6', 
'+23.4' );
+               $snak = new PropertyValueSnak( new PropertyId( 'P7' ), $value );
+
+               return array(
+                       'simple' => array(
+                               $snak,
+                               false,
+                               array(
+                                       '<http://www/Q1> 
<http://acme/statement/P7> "+23.5"^^<http://www.w3.org/2001/XMLSchema#decimal> 
.',
+                               )
+                       ),
+                       'complex' => array(
+                               $snak,
+                               true,
+                               array(
+                                       '<http://www/Q1> '
+                                               . '<http://acme/statement/P7> '
+                                               . 
'"+23.5"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       '<http://www/Q1> '
+                                               . 
'<http://acme/statement/value/P7> '
+                                               . 
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> .',
+                                       
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> '
+                                               . 
'<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> '
+                                               . 
'<http://acme/onto/QuantityValue> .',
+                                       
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> '
+                                               . 
'<http://acme/onto/quantityAmount> '
+                                               . 
'"+23.5"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> '
+                                               . 
'<http://acme/onto/quantityUpperBound> '
+                                               . 
'"+23.6"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> '
+                                               . 
'<http://acme/onto/quantityLowerBound> '
+                                               . 
'"+23.4"^^<http://www.w3.org/2001/XMLSchema#decimal> .',
+                                       
'<http://acme/value/1ac4bb05a87bfd5fde2740bbb6941533> '
+                                               . 
'<http://acme/onto/quantityUnit> '
+                                               . 
'<http://www.wikidata.org/entity/Q199> .',
+                               )
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider provideAddValue
+        */
+       public function testAddValue( PropertyValueSnak $snak, $complex, array 
$expected ) {
+               $vocab = new RdfVocabulary( 'http://acme.com/item/', 
'http://acme.com/data/' );
+
+               $snakWriter = new NTriplesRdfWriter();
+               $snakWriter->prefix( 'www', "http://www/"; );
+               $snakWriter->prefix( 'acme', "http://acme/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_VALUE, 
"http://acme/statement/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_STATEMENT, 
"http://acme/statement/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_VALUE, 
"http://acme/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_ONTOLOGY, 
"http://acme/onto/"; );
+
+               if ( $complex ) {
+                       $valueWriter = $snakWriter->sub();
+                       $helper = new ComplexValueRdfHelper( $vocab, 
$valueWriter, new HashDedupeBag() );
+               } else {
+                       $helper = null;
+               }
+
+               $builder = new QuantityRdfBuilder( $helper );
+
+               $snakWriter->start();
+               $snakWriter->about( 'www', 'Q1' );
+
+               $builder->addValue(
+                       $snakWriter,
+                       RdfVocabulary::NSP_CLAIM_STATEMENT,
+                       $vocab->getEntityLName( $snak->getPropertyid() ),
+                       'DUMMY',
+                       $snak
+               );
+
+               $triples = trim( $snakWriter->drain() );
+               $this->assertEquals( join( "\n", $expected ), $triples );
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/rdf/Values/TimeRdfBuilderTest.php 
b/repo/tests/phpunit/includes/rdf/Values/TimeRdfBuilderTest.php
new file mode 100644
index 0000000..371c7e0
--- /dev/null
+++ b/repo/tests/phpunit/includes/rdf/Values/TimeRdfBuilderTest.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Wikibase\Test\Rdf;
+
+use DataValues\TimeValue;
+use DataValues\StringValue;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Rdf\HashDedupeBag;
+use Wikibase\Rdf\JulianDateTimeValueCleaner;
+use Wikibase\Rdf\RdfVocabulary;
+use Wikibase\Rdf\Values\ComplexValueRdfHelper;
+use Wikibase\Rdf\Values\TimeRdfBuilder;
+use Wikimedia\Purtle\NTriplesRdfWriter;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+
+/**
+ * @covers Wikibase\Rdf\Values\TimeRdfBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseRdf
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class TimeRdfBuilderTest extends \PHPUnit_Framework_TestCase {
+
+       public function provideAddValue() {
+               $value = new TimeValue( '+2015-11-11T00:00:00Z', 0, 0, 0, 
TimeValue::PRECISION_DAY, TimeValue::CALENDAR_GREGORIAN );
+               $snak = new PropertyValueSnak( new PropertyId( 'P7' ), $value );
+
+               $valueJulian = new TimeValue( '+1345-11-11T00:00:00Z', 0, 0, 0, 
TimeValue::PRECISION_DAY, TimeValue::CALENDAR_JULIAN );
+               $snakJulian = new PropertyValueSnak( new PropertyId( 'P7' ), 
$valueJulian );
+
+               return array(
+                       'simple' => array(
+                               $snak,
+                               false,
+                               array(
+                                       '<http://www/Q1> 
<http://acme/statement/P7> 
"2015-11-11T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                               )
+                       ),
+                       'complex' => array(
+                               $snak,
+                               true,
+                               array(
+                                       '<http://www/Q1> '
+                                               . '<http://acme/statement/P7> '
+                                               . 
'"2015-11-11T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                                       '<http://www/Q1> '
+                                               . 
'<http://acme/statement/value/P7> '
+                                               . 
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> .',
+                                       
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> '
+                                               . 
'<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> '
+                                               . '<http://acme/onto/TimeValue> 
.',
+                                       
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> '
+                                               . '<http://acme/onto/timeValue> 
'
+                                               . 
'"2015-11-11T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                                       
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> '
+                                               . 
'<http://acme/onto/timePrecision> '
+                                               . 
'"11"^^<http://www.w3.org/2001/XMLSchema#integer> .',
+                                       
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> '
+                                               . 
'<http://acme/onto/timeTimezone> '
+                                               . 
'"0"^^<http://www.w3.org/2001/XMLSchema#integer> .',
+                                       
'<http://acme/value/6a84c07a464062e0f3df0cd1884dbdfd> '
+                                               . 
'<http://acme/onto/timeCalendarModel> '
+                                               . 
'<http://www.wikidata.org/entity/Q1985727> .',
+                               )
+                       ),
+                       'simple Julian' => array(
+                               $snakJulian,
+                               false,
+                               array(
+                                       '<http://www/Q1> 
<http://acme/statement/P7> 
"1345-11-19T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                               )
+                       ),
+                       'complex Julian' => array(
+                               $snakJulian,
+                               true,
+                               array(
+                                       '<http://www/Q1> '
+                                               . '<http://acme/statement/P7> '
+                                               . 
'"1345-11-19T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                                       '<http://www/Q1> '
+                                               . 
'<http://acme/statement/value/P7> '
+                                               . 
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> .',
+                                       
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> '
+                                               . 
'<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> '
+                                               . '<http://acme/onto/TimeValue> 
.',
+                                       
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> '
+                                               . '<http://acme/onto/timeValue> 
'
+                                               . 
'"1345-11-19T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .',
+                                       
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> '
+                                               . 
'<http://acme/onto/timePrecision> '
+                                               . 
'"11"^^<http://www.w3.org/2001/XMLSchema#integer> .',
+                                       
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> '
+                                               . 
'<http://acme/onto/timeTimezone> '
+                                               . 
'"0"^^<http://www.w3.org/2001/XMLSchema#integer> .',
+                                       
'<http://acme/value/b05c66f54b0960e1cb712466b7c192b4> '
+                                               . 
'<http://acme/onto/timeCalendarModel> '
+                                               . 
'<http://www.wikidata.org/entity/Q1985786> .',
+                               )
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider provideAddValue
+        */
+       public function testAddValue( PropertyValueSnak $snak, $complex, array 
$expected ) {
+               $vocab = new RdfVocabulary( 'http://acme.com/item/', 
'http://acme.com/data/' );
+
+               $snakWriter = new NTriplesRdfWriter();
+               $snakWriter->prefix( 'www', "http://www/"; );
+               $snakWriter->prefix( 'acme', "http://acme/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_VALUE, 
"http://acme/statement/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NSP_CLAIM_STATEMENT, 
"http://acme/statement/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_VALUE, 
"http://acme/value/"; );
+               $snakWriter->prefix( RdfVocabulary::NS_ONTOLOGY, 
"http://acme/onto/"; );
+
+               if ( $complex ) {
+                       $valueWriter = $snakWriter->sub();
+                       $helper = new ComplexValueRdfHelper( $vocab, 
$valueWriter, new HashDedupeBag() );
+               } else {
+                       $helper = null;
+               }
+
+               $dateCleaner = new JulianDateTimeValueCleaner();
+               $builder = new TimeRdfBuilder( $dateCleaner, $helper );
+
+               $snakWriter->start();
+               $snakWriter->about( 'www', 'Q1' );
+
+               $builder->addValue(
+                       $snakWriter,
+                       RdfVocabulary::NSP_CLAIM_STATEMENT,
+                       $vocab->getEntityLName( $snak->getPropertyid() ),
+                       'DUMMY',
+                       $snak
+               );
+
+               $triples = trim( $snakWriter->drain() );
+               $this->assertEquals( join( "\n", $expected ), $triples );
+       }
+
+}

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I6d9b99d657e3dcd1de9072ba5582c3f05a99523b
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to