Repository: apache/jena (java)

Issue:
RDF/XML: The presence of both rdf:parseType and rdf:datatype on a property element should be an error
### Version

5.1.0

### What happened?

Test case:

```
<?xml version="1.0"?>

<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xml:base="http://base/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:ex="http://example/">

  <rdf:Description rdf:about="s">
    <ex:property rdf:parseType="Literal" rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">
      123
    </ex:property>
  </rdf:Description>
</rdf:RDF>
```


### Relevant output and stacktrace

_No response_

### Are you interested in making a pull request?

Yes

Pull request:
GH-2669: Test for rdf:datatype with incompatible RDF attributes
GitHub issue resolved #2669

----

 - [x] Tests are included.
 - [ ] Documentation change and updates are provided for the [Apache Jena website](https://github.com/apache/jena-site/)
 - [x] Commits have been squashed to remove intermediate development commit messages.
 - [x] Key commit messages start with the issue number (GH-xxxx)

By submitting this pull request, I acknowledge that I am making a contribution to the Apache Software Foundation under the terms and conditions of the [Contributor's Agreement](https://www.apache.org/licenses/contributor-agreements.html).


Patch:
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJCC.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJCC.java
index b0218ea6c9..3e6fcdacab 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJCC.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJCC.java
@@ -19,15 +19,26 @@
 package org.apache.jena.riot.lang.extra;
 
 import org.apache.jena.riot.*;
+import org.apache.jena.riot.lang.LangTurtle;
 
+/**
+ * This is not the normal Turtle parser.
+ * <p>
+ * This is a separate Turtle parser using JavaCC used as a comparison. The JavaCC
+ * grammar reflects the RDF Turtle standard grammar. It also exists so that there is
+ * a valid JavaCC grammar that can be used as a basis for other languages.
+ * <p>
+ * It is significantly slower than the RIOT {@link LangTurtle}.
+ */
 public class TurtleJCC {
 
-    // Must be a different content type.
+    // Must be a different content type to the normal parser.
+    // Must have different name and altNames.
     // Must be a different file extension.
     public static Lang TTLJCC = LangBuilder.create("TurtleJavaCC", "text/turtle-jcc")
-                                        .addAltNames("ttljcc")
-                                        .addFileExtensions("ttljcc")
-                                        .build();
+                                           .addAltNames("ttljcc")
+                                           .addFileExtensions("ttljcc")
+                                           .build();
     public static ReaderRIOTFactory factory = (lang, profile) -> new TurtleJavaccReaderRIOT(profile) ;
 
     public static void register() {
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJavaccReaderRIOT.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJavaccReaderRIOT.java
index 0b7065a968..c213423716 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJavaccReaderRIOT.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/extra/TurtleJavaccReaderRIOT.java
@@ -35,10 +35,12 @@ import org.apache.jena.sparql.util.Context;
 
 /**
  * Turtle parser, written using JavaCC.
+ * <p>
  * This is not used normally.
- * It is slower than the RIOT {@link LangTurtle}.
- * It may not be up-to-date but at least in the codebase means it should be java-compatible.
- * It exists so that there is a JavaCC grammar that can be used as a basis for other languages.
+ * <p>
+ * It is slower than the RIOT {@link LangTurtle} and has not been optimized for
+ * speed. Tokenizing is the bottleneck. It exists so that there is a JavaCC grammar
+ * that can be used as a basis for other languages.
  */
 public class TurtleJavaccReaderRIOT implements ReaderRIOT {
     private final ParserProfile profile;
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx/ParserRRX_SAX.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx/ParserRRX_SAX.java
index f1bf90887f..d6aa5c68e1 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx/ParserRRX_SAX.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx/ParserRRX_SAX.java
@@ -765,11 +765,19 @@ class ParserRRX_SAX
         String rdfResourceStr = attributes.getValue(rdfNS, rdfResource);
         // Checked if the blank node is created.
         String objBlankNodeLabel = attributes.getValue(rdfNS, rdfNodeID);
-
         String parseTypeStr = attributes.getValue(rdfNS, rdfParseType);
-
         Node resourceObj = null;
 
+        // Better error messages.
+        if ( dt != null ) {
+            if ( parseTypeStr != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:parseType.", position);
+            if ( rdfResourceStr != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:resource.", position);
+            if ( objBlankNodeLabel != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:NodeId.", position);
+        }
+
         if ( rdfResourceStr != null && objBlankNodeLabel != null )
             throw RDFXMLparseError("Both rdf:resource and rdf:NodeId on a property element. Only one allowed", position);
 
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_ev/ParserRRX_StAX_EV.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_ev/ParserRRX_StAX_EV.java
index cbd3fd68e3..eaefd06559 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_ev/ParserRRX_StAX_EV.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_ev/ParserRRX_StAX_EV.java
@@ -538,19 +538,28 @@ class ParserRRX_StAX_EV {
 
         // If there is a blank node label, the element must be empty,
         // Check NCName if blank node created
-        String objBlanklNodeLabel = attribute(startElt, rdfNodeID);
+        String objBlankNodeLabel = attribute(startElt, rdfNodeID);
         String rdfResourceStr = attribute(startElt, rdfResource);
         String datatype = attribute(startElt, rdfDatatype);
         String parseType = objectParseType(startElt);
 
         // Checking
-        if ( rdfResourceStr != null && objBlanklNodeLabel != null )
+        if ( datatype != null ) {
+            if ( parseType != null && parseType != parseTypePlain )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:parseType.", startElt);
+            if ( rdfResourceStr != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:resource.", startElt);
+            if ( objBlankNodeLabel != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:NodeId.", startElt);
+        }
+
+        if ( rdfResourceStr != null && objBlankNodeLabel != null )
             throw RDFXMLparseError("Can't have both rdf:nodeId and rdf:resource on a property element", startElt);
 
         if ( rdfResourceStr != null && parseType != parseTypePlain )
             throw RDFXMLparseError("Both rdf:resource and rdf:ParseType on a property element. Only one allowed", startElt);
 
-        if ( objBlanklNodeLabel != null && parseType != parseTypePlain )
+        if ( objBlankNodeLabel != null && parseType != parseTypePlain )
             throw RDFXMLparseError("Both rdf:NodeId and rdf:ParseType on a property element. Only one allowed", startElt);
 
         Node resourceObj = null;
@@ -558,8 +567,8 @@ class ParserRRX_StAX_EV {
         if ( rdfResourceStr != null )
             resourceObj = iriResolve(rdfResourceStr, location);
 
-        if ( objBlanklNodeLabel != null )
-            resourceObj = blankNode(objBlanklNodeLabel, location);
+        if ( objBlankNodeLabel != null )
+            resourceObj = blankNode(objBlankNodeLabel, location);
 
         Node innerSubject = processPropertyAttributes(resourceObj, startElt, true, location);
         if ( resourceObj == null && innerSubject != null ) {
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_sr/ParserRRX_StAX_SR.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_sr/ParserRRX_StAX_SR.java
index 32c8eaf971..9f11b5954f 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_sr/ParserRRX_StAX_SR.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/rrx_stax_sr/ParserRRX_StAX_SR.java
@@ -512,19 +512,28 @@ class ParserRRX_StAX_SR {
 
         // If there is a blank node label, the element must be empty,
         // Check NCName if blank node created
-        String objBlanklNodeLabel = attribute(rdfNodeID);
+        String objBlankNodeLabel = attribute(rdfNodeID);
         String rdfResourceStr = attribute(rdfResource);
         String datatype = attribute(rdfDatatype);
         String parseType = objectParseType();
 
         // Checking
-        if ( rdfResourceStr != null && objBlanklNodeLabel != null )
+        if ( datatype != null ) {
+            if ( parseType != null && parseType != parseTypePlain )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:parseType.");
+            if ( rdfResourceStr != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:resource.");
+            if ( objBlankNodeLabel != null )
+                throw RDFXMLparseError("rdf:datatype can not be used with rdf:NodeId.");
+        }
+
+        if ( rdfResourceStr != null && objBlankNodeLabel != null )
             throw RDFXMLparseError("Can't have both rdf:nodeId and rdf:resource on a property element");
 
         if ( rdfResourceStr != null && parseType != parseTypePlain )
             throw RDFXMLparseError("Both rdf:resource and rdf:ParseType on a property element. Only one allowed");
 
-        if ( objBlanklNodeLabel != null && parseType != parseTypePlain )
+        if ( objBlankNodeLabel != null && parseType != parseTypePlain )
             throw RDFXMLparseError("Both rdf:NodeId and rdf:ParseType on a property element. Only one allowed");
 
         Node resourceObj = null;
@@ -532,8 +541,8 @@ class ParserRRX_StAX_SR {
         if ( rdfResourceStr != null )
             resourceObj = iriResolve(rdfResourceStr, location);
 
-        if ( objBlanklNodeLabel != null )
-            resourceObj = blankNode(objBlanklNodeLabel, location);
+        if ( objBlankNodeLabel != null )
+            resourceObj = blankNode(objBlankNodeLabel, location);
 
         Node innerSubject = processPropertyAttributes(resourceObj, qName, true, location);
         if ( resourceObj == null && innerSubject != null ) {


Test patch:
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/TestRRX.java b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/TestRRX.java
index 887ae1dfea..032c6e1743 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/TestRRX.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/TestRRX.java
@@ -193,6 +193,18 @@ public class TestRRX {
         checkForError("bad-unqualified-class.rdf", false);
     }
 
+    @Test public void bad_property_object_1() {
+        checkForError("bad-object-type-1.rdf", false);
+    }
+
+    @Test public void bad_property_object_2() {
+        checkForError("bad-object-type-2.rdf", false);
+    }
+
+    @Test public void bad_property_object_3() {
+        checkForError("bad-object-type-3.rdf", false);
+    }
+
     /** Parse with no base set by the parser */
     private void noBase(String filename) {
         trackFilename(filename);
diff --git a/jena-arq/testing/RIOT/rrx-files/bad-object-type-1.rdf b/jena-arq/testing/RIOT/rrx-files/bad-object-type-1.rdf
new file mode 100644
index 0000000000..5bceb260c8
--- /dev/null
+++ b/jena-arq/testing/RIOT/rrx-files/bad-object-type-1.rdf
@@ -0,0 +1,12 @@
+<rdf:RDF
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xml:base="http://base/"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+    xmlns:ex="http://example/">
+
+  <rdf:Description rdf:about="s">
+    <ex:property rdf:parseType="Literal" rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">
+      123
+    </ex:property>
+  </rdf:Description>
+</rdf:RDF>
diff --git a/jena-arq/testing/RIOT/rrx-files/bad-object-type-2.rdf b/jena-arq/testing/RIOT/rrx-files/bad-object-type-2.rdf
new file mode 100644
index 0000000000..b913f13f51
--- /dev/null
+++ b/jena-arq/testing/RIOT/rrx-files/bad-object-type-2.rdf
@@ -0,0 +1,12 @@
+<rdf:RDF
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xml:base="http://base/"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+    xmlns:ex="http://example/">
+
+  <rdf:Description rdf:about="s">
+    <ex:property rdf:parseType="Literal" rdf:resource="http://example/r">
+      123
+    </ex:property>
+  </rdf:Description>
+</rdf:RDF>
diff --git a/jena-arq/testing/RIOT/rrx-files/bad-object-type-3.rdf b/jena-arq/testing/RIOT/rrx-files/bad-object-type-3.rdf
new file mode 100644
index 0000000000..c8260dfaf4
--- /dev/null
+++ b/jena-arq/testing/RIOT/rrx-files/bad-object-type-3.rdf
@@ -0,0 +1,12 @@
+<rdf:RDF
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xml:base="http://base/"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+    xmlns:ex="http://example/">
+
+  <rdf:Description rdf:about="s">
+    <ex:property rdf:parseType="Literal" rdf:nodeID="b1234">
+      123
+    </ex:property>
+  </rdf:Description>
+</rdf:RDF>