diff --git a/extensions/wikidata/module/langs/translation-en.json b/extensions/wikidata/module/langs/translation-en.json
index e3a9cf53c..8ac957f43 100644
--- a/extensions/wikidata/module/langs/translation-en.json
+++ b/extensions/wikidata/module/langs/translation-en.json
@@ -216,6 +216,30 @@
"ignored-coordinates": {
"title": "Invalid geographic coordinates.",
"body": "Some coordinates are incorrectly formatted, such as {example_value}. See the allowed formats."
+ },
+ "forbidden-value": {
+ "title": "Invalid values for {property_entity}",
+ "body": "Items such as {example_value_entity} added on {example_subject_entity} are not allowed as values for {property_entity}."
+ },
+ "bounds-disallowed": {
+ "title": "Quantity bounds supplied for {property_entity}",
+ "body": "Values are not expected to have uncertainty bounds, but {example_value} added on {example_item_entity} has some. See the manual to learn how to fix their format."
+ },
+ "values-should-be-integers": {
+ "title": "Non-integer values of {property_entity}",
+ "body": "Values are expected to be integers, but {example_value} added on {example_item_entity} has a fractional part. See the manual to learn how to fix their format."
+ },
+ "invalid-unit": {
+ "title": "{property_entity} with invalid units",
+ "body": "Units such as {unit_entity} used on {example_item_entity} are invalid for {property_entity}."
+ },
+ "no-unit-provided": {
+ "title": "Unit missing for {property_entity}",
+ "body": "Values such as {example_value} on {example_item_entity} are expected to have units."
+ },
+ "invalid-entity-type": {
+ "title": "{property_entity} used on items",
+ "body": "Uses of {property_entity} on items such as {example_entity} are invalid."
}
}
}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java
index 9933bb02d..3e5e11bdd 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java
@@ -25,7 +25,9 @@ package org.openrefine.wikidata.qa;
import java.util.Set;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.Value;
/**
* An object that fetches constraints about properties.
@@ -53,6 +55,11 @@ public interface ConstraintFetcher {
* @return the pid of the inverse property
*/
PropertyIdValue getInversePid(PropertyIdValue pid);
+
+ /**
+ * Is this property supposed to be symmetric (its own inverse)?
+ */
+ boolean isSymmetric(PropertyIdValue pid);
/**
* Is this property for values only?
@@ -80,15 +87,51 @@ public interface ConstraintFetcher {
* (null if any)
*/
Set mandatoryQualifiers(PropertyIdValue pid);
+
+ /**
+ * Get the set of allowed values for this property (null if no such constraint).
+ * This set may contain null if one of the allowed values in novalue or somevalue.
+ */
+ Set allowedValues(PropertyIdValue pid);
+
+ /**
+ * Get the set of disallowed values for this property (null if no such constraint).
+ * This set may contain null if one of the allowed values in novalue or somevalue.
+ */
+ Set disallowedValues(PropertyIdValue pid);
/**
* Is this property expected to have at most one value per item?
*/
boolean hasSingleValue(PropertyIdValue pid);
+
+ /**
+ * Is this property expected to have a single best value only?
+ */
+ boolean hasSingleBestValue(PropertyIdValue pid);
/**
* Is this property expected to have distinct values?
*/
boolean hasDistinctValues(PropertyIdValue pid);
+ /**
+ * Can statements using this property have uncertainty bounds?
+ */
+ boolean boundsAllowed(PropertyIdValue pid);
+
+ /**
+ * Is this property expected to have integer values only?
+ */
+ boolean integerValued(PropertyIdValue pid);
+
+ /**
+ * Returns the allowed units for this property. If empty, no unit is allowed. If null, any unit is allowed.
+ */
+ Set allowedUnits(PropertyIdValue pid);
+
+ /**
+ * Can this property be used on items?
+ */
+ boolean usableOnItems(PropertyIdValue pid);
}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java
index 83bfa37e9..86b876b8c 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java
@@ -30,12 +30,15 @@ import java.util.stream.Collectors;
import org.openrefine.wikidata.qa.scrutinizers.DistinctValuesScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.EditScrutinizer;
+import org.openrefine.wikidata.qa.scrutinizers.EntityTypeScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.FormatScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.InverseConstraintScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.NewItemScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.NoEditsMadeScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.QualifierCompatibilityScrutinizer;
+import org.openrefine.wikidata.qa.scrutinizers.QuantityScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.RestrictedPositionScrutinizer;
+import org.openrefine.wikidata.qa.scrutinizers.RestrictedValuesScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.SelfReferentialScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.SingleValueScrutinizer;
import org.openrefine.wikidata.qa.scrutinizers.UnsourcedScrutinizer;
@@ -73,6 +76,9 @@ public class EditInspector {
register(new DistinctValuesScrutinizer());
register(new NoEditsMadeScrutinizer());
register(new WhitespaceScrutinizer());
+ register(new QuantityScrutinizer());
+ register(new RestrictedValuesScrutinizer());
+ register(new EntityTypeScrutinizer());
}
/**
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/WikidataConstraintFetcher.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/WikidataConstraintFetcher.java
index 8b301a530..9d2c3a963 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/qa/WikidataConstraintFetcher.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/WikidataConstraintFetcher.java
@@ -30,7 +30,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openrefine.wikidata.utils.EntityCache;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
import org.wikidata.wdtk.datamodel.interfaces.Snak;
@@ -56,6 +58,7 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
public static String INVERSE_CONSTRAINT_QID = "Q21510855";
public static String INVERSE_PROPERTY_PID = "P2306";
+ public static String SYMMETRIC_CONSTRAINT_QID = "Q21510862";
public static String USED_ONLY_AS_VALUES_CONSTRAINT_QID = "Q21528958";
@@ -68,9 +71,27 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
public static String MANDATORY_QUALIFIERS_CONSTRAINT_QID = "Q21510856";
public static String MANDATORY_QUALIFIERS_CONSTRAINT_PID = "P2306";
+
+ public static String ALLOWED_VALUES_CONSTRAINT_QID = "Q21510859";
+ public static String ALLOWED_VALUES_CONSTRAINT_PID = "P2305";
+
+ public static String DISALLOWED_VALUES_CONSTRAINT_QID = "Q52558054";
+ public static String DISALLOWED_VALUES_CONSTRAINT_PID = "P2305";
public static String SINGLE_VALUE_CONSTRAINT_QID = "Q19474404";
+ public static String SINGLE_BEST_VALUE_CONSTRAINT_QID = "Q52060874";
public static String DISTINCT_VALUES_CONSTRAINT_QID = "Q21502410";
+
+ public static String NO_BOUNDS_CONSTRAINT_QID = "Q51723761";
+ public static String INTEGER_VALUED_CONSTRAINT_QID = "Q52848401";
+
+ public static String ALLOWED_UNITS_CONSTRAINT_QID = "Q21514353";
+ public static String ALLOWED_UNITS_CONSTRAINT_PID = "P2305";
+
+ public static String ALLOWED_ENTITY_TYPES_QID = "Q52004125";
+ public static String ALLOWED_ITEM_TYPE_QID = "Q29934200";
+ public static String ALLOWED_ENTITY_TYPES_PID = "P2305";
+
// The following constraints still need to be implemented:
@@ -122,7 +143,10 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
if (specs != null) {
List properties = findValues(specs, ALLOWED_QUALIFIERS_CONSTRAINT_PID);
- return properties.stream().map(e -> (PropertyIdValue) e).collect(Collectors.toSet());
+ return properties.stream()
+ .filter(e -> e != null)
+ .map(e -> (PropertyIdValue) e)
+ .collect(Collectors.toSet());
}
return null;
}
@@ -133,7 +157,10 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
if (specs != null) {
List properties = findValues(specs, MANDATORY_QUALIFIERS_CONSTRAINT_PID);
- return properties.stream().map(e -> (PropertyIdValue) e).collect(Collectors.toSet());
+ return properties.stream()
+ .filter(e -> e != null)
+ .map(e -> (PropertyIdValue) e)
+ .collect(Collectors.toSet());
}
return null;
}
@@ -142,11 +169,74 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
public boolean hasSingleValue(PropertyIdValue pid) {
return getSingleConstraint(pid, SINGLE_VALUE_CONSTRAINT_QID) != null;
}
+
+ @Override
+ public boolean hasSingleBestValue(PropertyIdValue pid) {
+ return getSingleConstraint(pid, SINGLE_BEST_VALUE_CONSTRAINT_QID) != null;
+ }
@Override
public boolean hasDistinctValues(PropertyIdValue pid) {
return getSingleConstraint(pid, DISTINCT_VALUES_CONSTRAINT_QID) != null;
}
+
+ @Override
+ public boolean isSymmetric(PropertyIdValue pid) {
+ return getSingleConstraint(pid, SYMMETRIC_CONSTRAINT_QID) != null;
+ }
+
+ @Override
+ public Set allowedValues(PropertyIdValue pid) {
+ List specs = getSingleConstraint(pid, ALLOWED_VALUES_CONSTRAINT_QID);
+
+ if (specs != null) {
+ List properties = findValues(specs, ALLOWED_VALUES_CONSTRAINT_PID);
+ return properties.stream().collect(Collectors.toSet());
+ }
+ return null;
+ }
+
+ @Override
+ public Set disallowedValues(PropertyIdValue pid) {
+ List specs = getSingleConstraint(pid, DISALLOWED_VALUES_CONSTRAINT_QID);
+
+ if (specs != null) {
+ List properties = findValues(specs, DISALLOWED_VALUES_CONSTRAINT_PID);
+ return properties.stream().collect(Collectors.toSet());
+ }
+ return null;
+ }
+
+ @Override
+ public boolean boundsAllowed(PropertyIdValue pid) {
+ return getSingleConstraint(pid, NO_BOUNDS_CONSTRAINT_QID) == null;
+ }
+
+ @Override
+ public boolean integerValued(PropertyIdValue pid) {
+ return getSingleConstraint(pid, INTEGER_VALUED_CONSTRAINT_QID) != null;
+ }
+
+ @Override
+ public Set allowedUnits(PropertyIdValue pid) {
+ List specs = getSingleConstraint(pid, ALLOWED_UNITS_CONSTRAINT_QID);
+
+ if (specs != null) {
+ List properties = findValues(specs, ALLOWED_UNITS_CONSTRAINT_PID);
+ return properties.stream().map(e -> e == null ? null : (ItemIdValue) e).collect(Collectors.toSet());
+ }
+ return null;
+ }
+
+ @Override
+ public boolean usableOnItems(PropertyIdValue pid) {
+ List constraint = getSingleConstraint(pid, ALLOWED_ENTITY_TYPES_QID);
+ if (constraint != null) {
+ return findValues(constraint, ALLOWED_ENTITY_TYPES_PID).contains(
+ Datamodel.makeWikidataItemIdValue(ALLOWED_ITEM_TYPE_QID));
+ }
+ return true;
+ }
/**
* Returns a single constraint for a particular type and a property, or null if
@@ -193,7 +283,9 @@ public class WikidataConstraintFetcher implements ConstraintFetcher {
PropertyDocument doc = (PropertyDocument) EntityCache.getEntityDocument(pid);
StatementGroup group = doc.findStatementGroup(WIKIDATA_CONSTRAINT_PID);
if (group != null) {
- return group.getStatements();
+ return group.getStatements().stream()
+ .filter(s -> s.getValue() != null && s.getValue() instanceof EntityIdValue)
+ .collect(Collectors.toList());
} else {
return new ArrayList();
}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizer.java
new file mode 100644
index 000000000..af822dc28
--- /dev/null
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizer.java
@@ -0,0 +1,22 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import org.openrefine.wikidata.qa.QAWarning;
+import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.Snak;
+
+
+public class EntityTypeScrutinizer extends SnakScrutinizer {
+
+ public final static String type = "invalid-entity-type";
+
+ @Override
+ public void scrutinize(Snak snak, EntityIdValue entityId, boolean added) {
+ PropertyIdValue pid = snak.getPropertyId();
+ if(!_fetcher.usableOnItems(pid)) {
+ QAWarning issue = new QAWarning(type, null, QAWarning.Severity.WARNING, 1);
+ issue.setProperty("example_entity", entityId);
+ addIssue(issue);
+ }
+ }
+}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstraintScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstraintScrutinizer.java
index 6a1a7b619..88bc2c3ae 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstraintScrutinizer.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstraintScrutinizer.java
@@ -59,6 +59,9 @@ public class InverseConstraintScrutinizer extends StatementScrutinizer {
return _inverse.get(pid);
} else {
PropertyIdValue inversePid = _fetcher.getInversePid(pid);
+ if (inversePid == null && _fetcher.isSymmetric(pid)) {
+ inversePid = pid;
+ }
_inverse.put(pid, inversePid);
_statements.put(pid, new HashMap>());
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizer.java
new file mode 100644
index 000000000..ea711fe56
--- /dev/null
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizer.java
@@ -0,0 +1,70 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.openrefine.wikidata.qa.QAWarning;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.QuantityValue;
+import org.wikidata.wdtk.datamodel.interfaces.Snak;
+
+/**
+ * Scrutinizer checking for units and bounds in quantities.
+ *
+ * @author Antonin Delpeuch
+ *
+ */
+public class QuantityScrutinizer extends SnakScrutinizer {
+
+ public static final String boundsDisallowedType = "bounds-disallowed";
+ public static final String integerConstraintType = "values-should-be-integers";
+ public static final String invalidUnitType = "invalid-unit";
+ public static final String noUnitProvidedType = "no-unit-provided";
+
+ @Override
+ public void scrutinize(Snak snak, EntityIdValue entityId, boolean added) {
+ if (QuantityValue.class.isInstance(snak.getValue()) && added) {
+ PropertyIdValue pid = snak.getPropertyId();
+ QuantityValue value = (QuantityValue)snak.getValue();
+
+ if(!_fetcher.boundsAllowed(pid) && (value.getUpperBound() != null || value.getLowerBound() != null)) {
+ QAWarning issue = new QAWarning(boundsDisallowedType, pid.getId(), QAWarning.Severity.IMPORTANT, 1);
+ issue.setProperty("property_entity", pid);
+ issue.setProperty("example_value", value.getNumericValue().toString());
+ issue.setProperty("example_item_entity", entityId);
+ addIssue(issue);
+ }
+ if(_fetcher.integerValued(pid) && value.getNumericValue().scale() > 0) {
+ QAWarning issue = new QAWarning(integerConstraintType, pid.getId(), QAWarning.Severity.IMPORTANT, 1);
+ issue.setProperty("property_entity", pid);
+ issue.setProperty("example_value", value.getNumericValue().toString());
+ issue.setProperty("example_item_entity", entityId);
+ addIssue(issue);
+ }
+ Set allowedUnits = _fetcher.allowedUnits(pid);
+ String currentUnit = null;
+ if (value.getUnit() != null && !value.getUnit().equals("")) {
+ currentUnit = value.getUnit();
+ }
+ if(allowedUnits != null &&
+ !allowedUnits.stream().map(u -> u != null ? u.getIri() : null)
+ .collect(Collectors.toSet()).contains(currentUnit)) {
+ String issueType = currentUnit == null ? noUnitProvidedType : invalidUnitType;
+ QAWarning issue = new QAWarning(issueType, pid.getId(), QAWarning.Severity.IMPORTANT, 1);
+ issue.setProperty("property_entity", pid);
+ issue.setProperty("example_value", value.getNumericValue().toString());
+ issue.setProperty("example_item_entity", entityId);
+ if (currentUnit != null) {
+ issue.setProperty("unit_entity",
+ // this is a hack but it will not be needed anymore in the upcoming version of Wikidata-Toolkit
+ Datamodel.makeWikidataItemIdValue(currentUnit.substring(currentUnit.indexOf("Q"))));
+ }
+ addIssue(issue);
+ }
+ }
+ }
+
+}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizer.java
new file mode 100644
index 000000000..5aed7b8b4
--- /dev/null
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizer.java
@@ -0,0 +1,32 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import java.util.Set;
+
+import org.openrefine.wikidata.qa.QAWarning;
+import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.Snak;
+import org.wikidata.wdtk.datamodel.interfaces.Value;
+
+
+public class RestrictedValuesScrutinizer extends SnakScrutinizer {
+
+ public static String type = "forbidden-value";
+
+ @Override
+ public void scrutinize(Snak snak, EntityIdValue entityId, boolean added) {
+ PropertyIdValue pid = snak.getPropertyId();
+ Value value = snak.getValue();
+
+ Set allowedValues = _fetcher.allowedValues(pid);
+ Set disallowedValues = _fetcher.disallowedValues(pid);
+ if((allowedValues != null && !allowedValues.contains(value)) ||
+ (disallowedValues != null && disallowedValues.contains(value))) {
+ QAWarning issue = new QAWarning(type, pid.getId(), QAWarning.Severity.IMPORTANT, 1);
+ issue.setProperty("property_entity", pid);
+ issue.setProperty("example_value_entity", value);
+ issue.setProperty("example_subject_entity", entityId);
+ addIssue(issue);
+ }
+ }
+}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/SingleValueScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/SingleValueScrutinizer.java
index bdd7bb3f4..0609205d1 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/SingleValueScrutinizer.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/SingleValueScrutinizer.java
@@ -35,6 +35,9 @@ import org.wikidata.wdtk.datamodel.interfaces.Statement;
* For now this scrutinizer only checks for uniqueness at the item level (it
* ignores qualifiers and references).
*
+ * Given that all ranks are currently set to Normal, this also checks for
+ * single best values.
+ *
* @author Antonin Delpeuch
*
*/
@@ -54,7 +57,7 @@ public class SingleValueScrutinizer extends EditScrutinizer {
issue.setProperty("property_entity", pid);
issue.setProperty("example_entity", update.getItemId());
addIssue(issue);
- } else if (_fetcher.hasSingleValue(pid)) {
+ } else if (_fetcher.hasSingleValue(pid) || _fetcher.hasSingleBestValue(pid)) {
seenSingleProperties.add(pid);
}
}
diff --git a/extensions/wikidata/src/org/openrefine/wikidata/schema/WbItemVariable.java b/extensions/wikidata/src/org/openrefine/wikidata/schema/WbItemVariable.java
index 00c634580..af7d2fd72 100644
--- a/extensions/wikidata/src/org/openrefine/wikidata/schema/WbItemVariable.java
+++ b/extensions/wikidata/src/org/openrefine/wikidata/schema/WbItemVariable.java
@@ -63,7 +63,7 @@ public class WbItemVariable extends WbVariableExpr {
throws SkipSchemaExpressionException {
if (cell.recon != null
&& (Judgment.Matched.equals(cell.recon.judgment) || Judgment.New.equals(cell.recon.judgment))) {
- if (!cell.recon.identifierSpace.equals(Datamodel.SITE_WIKIDATA)) {
+ if (cell.recon.identifierSpace == null || !cell.recon.identifierSpace.equals(Datamodel.SITE_WIKIDATA)) {
QAWarning warning = new QAWarning("invalid-identifier-space", null, QAWarning.Severity.INFO, 1);
warning.setProperty("example_cell", cell.value.toString());
ctxt.addWarning(warning);
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/MockConstraintFetcher.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/MockConstraintFetcher.java
index b13c7ccff..330d96f3a 100644
--- a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/MockConstraintFetcher.java
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/MockConstraintFetcher.java
@@ -29,18 +29,35 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.Value;
public class MockConstraintFetcher implements ConstraintFetcher {
public static PropertyIdValue pidWithInverse = Datamodel.makeWikidataPropertyIdValue("P350");
public static PropertyIdValue inversePid = Datamodel.makeWikidataPropertyIdValue("P57");
+ public static PropertyIdValue symmetricPid = Datamodel.makeWikidataPropertyIdValue("P783");
public static PropertyIdValue allowedQualifierPid = Datamodel.makeWikidataPropertyIdValue("P34");
public static PropertyIdValue mandatoryQualifierPid = Datamodel.makeWikidataPropertyIdValue("P97");
public static PropertyIdValue mainSnakPid = Datamodel.makeWikidataPropertyIdValue("P1234");
public static PropertyIdValue qualifierPid = Datamodel.makeWikidataPropertyIdValue("P987");
public static PropertyIdValue referencePid = Datamodel.makeWikidataPropertyIdValue("P384");
+
+ public static PropertyIdValue allowedValuesPid = Datamodel.makeWikidataPropertyIdValue("P8121");
+ public static ItemIdValue allowedValueQid = Datamodel.makeWikidataItemIdValue("Q389");
+ public static PropertyIdValue forbiddenValuesPid = Datamodel.makeWikidataPropertyIdValue("P8141");
+ public static ItemIdValue forbiddenValueQid = Datamodel.makeWikidataItemIdValue("Q378");
+
+ public static PropertyIdValue allowedUnitsPid = Datamodel.makeWikidataPropertyIdValue("P34787");
+ public static ItemIdValue allowedUnit = Datamodel.makeWikidataItemIdValue("Q7887");
+ public static PropertyIdValue noUnitsPid = Datamodel.makeWikidataPropertyIdValue("P334211");
+
+ public static PropertyIdValue noBoundsPid = Datamodel.makeWikidataPropertyIdValue("P8932");
+ public static PropertyIdValue integerPid = Datamodel.makeWikidataPropertyIdValue("P389");
+
+ public static PropertyIdValue propertyOnlyPid = Datamodel.makeWikidataPropertyIdValue("P372");
@Override
public String getFormatRegex(PropertyIdValue pid) {
@@ -88,10 +105,60 @@ public class MockConstraintFetcher implements ConstraintFetcher {
public boolean hasSingleValue(PropertyIdValue pid) {
return true;
}
+
+ @Override
+ public boolean hasSingleBestValue(PropertyIdValue pid) {
+ return false;
+ }
@Override
public boolean hasDistinctValues(PropertyIdValue pid) {
return true;
}
+ @Override
+ public boolean isSymmetric(PropertyIdValue pid) {
+ return pid.equals(symmetricPid);
+ }
+
+ @Override
+ public Set allowedValues(PropertyIdValue pid) {
+ if (allowedValuesPid.equals(pid)) {
+ return Arrays.asList(allowedValueQid, null).stream().collect(Collectors.toSet());
+ }
+ return null;
+ }
+
+ @Override
+ public Set disallowedValues(PropertyIdValue pid) {
+ if (forbiddenValuesPid.equals(pid)) {
+ return Collections.singleton(forbiddenValueQid);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean boundsAllowed(PropertyIdValue pid) {
+ return !noBoundsPid.equals(pid);
+ }
+
+ @Override
+ public boolean integerValued(PropertyIdValue pid) {
+ return integerPid.equals(pid);
+ }
+
+ @Override
+ public Set allowedUnits(PropertyIdValue pid) {
+ if(allowedUnitsPid.equals(pid)) {
+ return Collections.singleton(allowedUnit);
+ } else if(noUnitsPid.equals(pid)) {
+ return Collections.singleton(null);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean usableOnItems(PropertyIdValue pid) {
+ return !propertyOnlyPid.equals(pid);
+ }
}
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizerTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizerTest.java
new file mode 100644
index 000000000..baaf6f44e
--- /dev/null
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/EntityTypeScrutinizerTest.java
@@ -0,0 +1,29 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import org.openrefine.wikidata.qa.MockConstraintFetcher;
+import org.openrefine.wikidata.testing.TestingData;
+import org.testng.annotations.Test;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
+
+public class EntityTypeScrutinizerTest extends StatementScrutinizerTest {
+
+ private static ItemIdValue qid = Datamodel.makeWikidataItemIdValue("Q343");
+
+ @Override
+ public EditScrutinizer getScrutinizer() {
+ return new EntityTypeScrutinizer();
+ }
+
+ @Test
+ public void testAllowed() {
+ scrutinize(TestingData.generateStatement(qid, qid));
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testDisallowed() {
+ scrutinize(TestingData.generateStatement(qid, MockConstraintFetcher.propertyOnlyPid, qid));
+ assertWarningsRaised(EntityTypeScrutinizer.type);
+ }
+}
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstaintScrutinizerTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstaintScrutinizerTest.java
index a44bddbd6..a4313d9f5 100644
--- a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstaintScrutinizerTest.java
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/InverseConstaintScrutinizerTest.java
@@ -37,6 +37,7 @@ public class InverseConstaintScrutinizerTest extends StatementScrutinizerTest {
private ItemIdValue idB = TestingData.newIdB;
private PropertyIdValue pidWithInverse = MockConstraintFetcher.pidWithInverse;
private PropertyIdValue inversePid = MockConstraintFetcher.inversePid;
+ private PropertyIdValue symmetricPid = MockConstraintFetcher.symmetricPid;
@Override
public EditScrutinizer getScrutinizer() {
@@ -50,6 +51,14 @@ public class InverseConstaintScrutinizerTest extends StatementScrutinizerTest {
scrutinize(update);
assertWarningsRaised(InverseConstraintScrutinizer.type);
}
+
+ @Test
+ public void testSymmetric() {
+ ItemUpdate update = new ItemUpdateBuilder(idA)
+ .addStatement(TestingData.generateStatement(idA, symmetricPid, idB)).build();
+ scrutinize(update);
+ assertWarningsRaised(InverseConstraintScrutinizer.type);
+ }
@Test
public void testNoSymmetricClosure() {
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizerTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizerTest.java
new file mode 100644
index 000000000..f0dbb0acf
--- /dev/null
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/QuantityScrutinizerTest.java
@@ -0,0 +1,102 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import java.math.BigDecimal;
+
+import org.openrefine.wikidata.qa.MockConstraintFetcher;
+import org.testng.annotations.Test;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.interfaces.QuantityValue;
+
+public class QuantityScrutinizerTest extends ValueScrutinizerTest{
+
+ private QuantityValue exactValue = Datamodel.makeQuantityValue(
+ new BigDecimal("1.234"));
+
+ private QuantityValue integerValue = Datamodel.makeQuantityValue(
+ new BigDecimal("132"));
+
+ private QuantityValue trailingZeros = Datamodel.makeQuantityValue(
+ new BigDecimal("132.00"));
+
+ private QuantityValue valueWithBounds = Datamodel.makeQuantityValue(
+ new BigDecimal("1.234"),
+ new BigDecimal("1.200"),
+ new BigDecimal("1.545"));
+
+ private QuantityValue wrongUnitValue = Datamodel.makeQuantityValue(
+ new BigDecimal("1.234"), "Q346721");
+
+ private QuantityValue goodUnitValue = Datamodel.makeQuantityValue(
+ new BigDecimal("1.234"), MockConstraintFetcher.allowedUnit.getIri());
+
+ @Override
+ public EditScrutinizer getScrutinizer() {
+ return new QuantityScrutinizer();
+ }
+
+ @Test
+ public void testBoundsAllowed() {
+ scrutinize(valueWithBounds);
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testBoundsDisallowed() {
+ scrutinize(MockConstraintFetcher.noBoundsPid, valueWithBounds);
+ assertWarningsRaised(QuantityScrutinizer.boundsDisallowedType);
+ }
+
+ @Test
+ public void testFractionalAllowed() {
+ scrutinize(exactValue);
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testFractionalDisallowed() {
+ scrutinize(MockConstraintFetcher.integerPid, exactValue);
+ assertWarningsRaised(QuantityScrutinizer.integerConstraintType);
+ }
+
+ @Test
+ public void testTrailingZeros() {
+ scrutinize(MockConstraintFetcher.integerPid, trailingZeros);
+ assertWarningsRaised(QuantityScrutinizer.integerConstraintType);
+ }
+
+ @Test
+ public void testInteger() {
+ scrutinize(MockConstraintFetcher.integerPid, integerValue);
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testUnitReqired() {
+ scrutinize(MockConstraintFetcher.allowedUnitsPid, integerValue);
+ assertWarningsRaised(QuantityScrutinizer.noUnitProvidedType);
+ }
+
+ @Test
+ public void testWrongUnit() {
+ scrutinize(MockConstraintFetcher.allowedUnitsPid, wrongUnitValue);
+ assertWarningsRaised(QuantityScrutinizer.invalidUnitType);
+ }
+
+ @Test
+ public void testGoodUnit() {
+ scrutinize(MockConstraintFetcher.allowedUnitsPid, goodUnitValue);
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testUnitForbidden() {
+ scrutinize(MockConstraintFetcher.noUnitsPid, goodUnitValue);
+ assertWarningsRaised(QuantityScrutinizer.invalidUnitType);
+ }
+
+ @Test
+ public void testNoUnit() {
+ scrutinize(MockConstraintFetcher.noUnitsPid, integerValue);
+ assertNoWarningRaised();
+ }
+}
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizerTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizerTest.java
new file mode 100644
index 000000000..5ba9b58eb
--- /dev/null
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/RestrictedValuesScrutinizerTest.java
@@ -0,0 +1,58 @@
+package org.openrefine.wikidata.qa.scrutinizers;
+
+import org.openrefine.wikidata.qa.MockConstraintFetcher;
+import org.openrefine.wikidata.testing.TestingData;
+import org.testng.annotations.Test;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
+
+public class RestrictedValuesScrutinizerTest extends SnakScrutinizerTest {
+
+ private ItemIdValue qid = Datamodel.makeWikidataItemIdValue("Q3487");
+
+ @Override
+ public EditScrutinizer getScrutinizer() {
+ return new RestrictedValuesScrutinizer();
+ }
+
+ @Test
+ public void testNoConstraint() {
+ scrutinize(TestingData.generateStatement(qid,
+ Datamodel.makeWikidataPropertyIdValue("P28732"),
+ qid));
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testAllowedValue() {
+ scrutinize(TestingData.generateStatement(qid,
+ MockConstraintFetcher.allowedValuesPid,
+ MockConstraintFetcher.allowedValueQid));
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testAllowedValueFailing() {
+ scrutinize(TestingData.generateStatement(qid,
+ MockConstraintFetcher.allowedValuesPid,
+ qid));
+ assertWarningsRaised(RestrictedValuesScrutinizer.type);
+ }
+
+ @Test
+ public void testDisallowedValue() {
+ scrutinize(TestingData.generateStatement(qid,
+ MockConstraintFetcher.forbiddenValuesPid,
+ qid));
+ assertNoWarningRaised();
+ }
+
+ @Test
+ public void testDisallowedValueFailing() {
+ scrutinize(TestingData.generateStatement(qid,
+ MockConstraintFetcher.forbiddenValuesPid,
+ MockConstraintFetcher.forbiddenValueQid));
+ assertWarningsRaised(RestrictedValuesScrutinizer.type);
+ }
+
+}
diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/ValueScrutinizerTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/ValueScrutinizerTest.java
index 21ec3fdb3..bba04c5c6 100644
--- a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/ValueScrutinizerTest.java
+++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/scrutinizers/ValueScrutinizerTest.java
@@ -35,7 +35,11 @@ public abstract class ValueScrutinizerTest extends SnakScrutinizerTest {
public static final PropertyIdValue defaultPid = Datamodel.makeWikidataPropertyIdValue("P328");
public void scrutinize(Value value) {
- scrutinize(Datamodel.makeValueSnak(defaultPid, value));
+ scrutinize(defaultPid, value);
+ }
+
+ public void scrutinize(PropertyIdValue pid, Value value) {
+ scrutinize(Datamodel.makeValueSnak(pid, value));
}
public void scrutinizeLabel(MonolingualTextValue text) {