This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit 452816cf97d8dba621d1c25b4b0032a24c0b9c7b Author: Andy Seaborne <[email protected]> AuthorDate: Sun Aug 11 12:31:32 2024 +0100 GH-2630: Handle unqualified attributes --- .../apache/jena/riot/lang/rdfxml/package-info.java | 9 +- .../jena/riot/lang/rdfxml/rrx/ParserRRX_SAX.java | 274 ++++++++++++--------- .../lang/rdfxml/rrx_stax_ev/ParserRRX_StAX_EV.java | 245 +++++++++++------- .../lang/rdfxml/rrx_stax_sr/ParserRRX_StAX_SR.java | 235 +++++++++++------- .../jena/riot/lang/rdfxml/rrx/RunTestRDFXML.java | 6 +- .../apache/jena/riot/lang/rdfxml/rrx/TestRRX.java | 22 +- .../testing/RIOT/rrx-files/bad-rdf-id-node.rdf | 2 +- .../RIOT/rrx-files/bad-rdf-resource-node.rdf | 2 +- .../RIOT/rrx-files/bad-unqualified-attribute1.rdf | 2 +- .../RIOT/rrx-files/bad-unqualified-attribute2.rdf | 2 +- ...tribute2.rdf => bad-unqualified-attribute3.rdf} | 6 +- ...tribute2.rdf => bad-unqualified-attribute4.rdf} | 6 +- ...tribute2.rdf => bad-unqualified-attribute5.rdf} | 6 +- 13 files changed, 495 insertions(+), 322 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/package-info.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/package-info.java index 1fad47ab40..348641f1be 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/package-info.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/rdfxml/package-info.java @@ -22,16 +22,19 @@ * that is the RIOT reader interface implementation and a {@code RDFXMLParser-???} * class which is the parser engine. * <ul> - * <li>SAX based {@linkplain org.apache.jena.riot.lang.rdfxml.rrx.ParserRDFXML_SAX ParserRDFXML_SAX}. This is the default parser for Jena5 onwards for + * <li>SAX based {@linkplain org.apache.jena.riot.lang.rdfxml.rrx.ParserRRX_SAX ParserRRX_SAX}. This is the default parser for Jena5 onwards for * RDF/XML in RIOT, i.e it is the registration for {@linkplain org.apache.jena.riot.Lang#RDFXML Lang.RDFXML} and it is the fastest. * <li>A StAX-based parser using {@link javax.xml.stream.XMLStreamReader XMLStreamReader}. * <li>A StAX-based parser using {@link javax.xml.stream.XMLEventReader XMLEventReader}. * </ul> - * In addition Jena (in jena-core) has the original ARP parser in package {@code org.apach.jena-rdfxml.rdfxml0} referred to as "ARP0". - * This was the RDF/XML parser up to Jena 4.6.1. ARP1 uses package jena-iri directly to handle IRIs. + * In addition, Jena (in jena-core) has the original ARP parser. + * <p> + * "ARP0" in package {@code org.apach.jena-rdfxml.rdfxml0} + * This was the RDF/XML parser up to Jena 4.6.1. It handled IRIs directly using jena-iri. * <p> * "ARP1" is in package {@code org.apach.jena-rdfxml.rdfxml1} is derived from ARP0. * It uses the {@link org.apache.jena.irix.IRIx IRIx} abstraction to handle IRIs. + * It has error reporting aligned with RIOT. * <br/> * </p> * <table border=1 style="border-width: 2px ; border-style: solid; border-collapse: collapse"> 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 9f34f9cd2e..6d3e768ef5 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 @@ -108,6 +108,7 @@ class ParserRRX_SAX private static Set<String> $coreSyntaxTerms = Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype); + // Not used. // 6.2.3 Production syntaxTerms // coreSyntaxTerms | rdf:Description | rdf:li private static Set<String> $syntaxTerms = @@ -118,9 +119,16 @@ class ParserRRX_SAX // rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID private static Set<String> $oldTerms = Set.of(rdfAboutEach, rdfAboutEachPrefix, rdfBagID); - private static Set<String> disallowedPropertyAttributeTerms = - Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype, - rdfDescription, rdfContainerItem, "aboutEach", "aboutEachPrefix", "bagID"); + // 6.1.4 Attribute Event + // The (old form) qualified named allowed where property attributes expected. + // Parses "MAY" warn about their use + private static Set<String> $allowedUnqualified = Set.of(rdfAbout, rdfID, rdfResource, rdfParseType, rdfType); + + private boolean coreSyntaxTerm(String namespace, String localName) { + if ( ! rdfNS.equals(namespace) ) + return false; + return $coreSyntaxTerms.contains(localName); + } // 6.2.5 Production nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) @@ -166,11 +174,20 @@ class ParserRRX_SAX return true; } + private static boolean allowedUnqualifiedTerm(String localName) { + return $allowedUnqualified.contains(localName); + } + + /** The attributes that guide the RDF/XML parser. */ + // Production: CodeSyntaxterms + // rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype + /** The attributes that guide the RDF/XML parser. */ private static Set<String> $rdfSyntaxAttributes = Set.of(rdfRDF, rdfAbout, rdfNodeID, rdfID, rdfParseType, rdfDatatype, rdfResource); - private boolean isSyntaxAttribute(String namespace, String localName) { - if ( ! rdfNS.equals(namespace) ) + + private static boolean isSyntaxAttribute(String namespace, String localName) { + if ( ! isXMLNamespace(namespace) ) return false; return $rdfSyntaxAttributes.contains(localName); } @@ -250,6 +267,13 @@ class ParserRRX_SAX ObjectNode } + /** Mark the usage of a QName */ + private enum QNameUsage { + TypedNodeElement("typed node element"), PropertyElement("property element"); + final String msg; + private QNameUsage(String msg) { this.msg = msg; } + } + /** Integer holder for rdf:li */ private static class Counter { int value = 1; } @@ -424,8 +448,6 @@ class ParserRRX_SAX this.parserMode = parserMode; } -// // Forming objects. -// private ParseType parseType = null; ParserRRX_SAX(String xmlBase, ParserProfile parserProfile, StreamRDF destination, Context context) { // Debug if ( ReaderRDFXML_SAX.TRACE ) { @@ -696,12 +718,12 @@ class ParserRRX_SAX if ( isNotRecognizedRDFtype(namespaceURI, localName) ) RDFXMLparseWarning(qName+" is not a recognized RDF term for a type", position); } - Node object = qNameToIRI(namespaceURI, localName, position, "typed node element"); + Node object = qNameToIRI(namespaceURI, localName, QNameUsage.TypedNodeElement, position); emit(currentSubject, RDF.Nodes.type, object, position); } - if ( hasPropertyAttributes(attributes, position) ) - processPropertyAttributes(currentSubject, attributes, position); + processPropertyAttributes(currentSubject, qName, attributes, false, position); + parserMode(ParserMode.PropertyElement); } @@ -726,7 +748,7 @@ class ParserRRX_SAX currentProperty = iri(p, position); } else { // The empty string namespace does not apply to XML attributes. - currentProperty = qNameToIRI(namespaceURI, localName, position, "property element"); + currentProperty = qNameToIRI(namespaceURI, localName, QNameUsage.PropertyElement, position); } if ( ReaderRDFXML_SAX.TRACE ) @@ -763,14 +785,9 @@ class ParserRRX_SAX if ( objBlankNodeLabel != null ) resourceObj = blankNode(objBlankNodeLabel, position); - if ( hasPropertyAttributes(attributes, position) ) { - if ( parseTypeStr != null ) { - // rdf:parseType found. - throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+qName, position); - } + Node innerSubject = processPropertyAttributes(resourceObj, qName, attributes, true, position); + if (resourceObj == null && innerSubject != null ) { // AND must be empty tag - Node innerSubject = (resourceObj==null) ? blankNode(position) : resourceObj; - processPropertyAttributes(innerSubject, attributes, position); currentEmitter.emit(currentSubject, currentProperty, innerSubject, position); return; } @@ -828,34 +845,27 @@ class ParserRRX_SAX return currentProperty == null; } -// private String xmlBaseStr(Attributes attributes, Position position) { -// String baseStr = attributes.getValue(xmlNS, xmlBaseLN); -// if ( baseStr == null ) -// return null; -// return IRIs.resolve(currentBase, baseStr); -// } - - // Start element encountered when expecting a ObjectCollection - private void startCollectionItem(String namespaceURI, String localName, String qName, Attributes attributes, Position position) { - // Finish last list cell, start new one. - if ( ReaderRDFXML_SAX.TRACE ) - trace.println("Generate list cell"); - // Preceding cell in list. - Node previousCollectionNode = collectionNode.node; - Node thisCollectionNode = blankNode(position); - // New cell in list. - // Either link up to the origin or fixup previous cell. - if ( previousCollectionNode == null ) - currentEmitter.emit(currentSubject, currentProperty, thisCollectionNode, position); - else - emit(previousCollectionNode, Nodes.rest, thisCollectionNode, position); - collectionNode.node = thisCollectionNode; + // Start element encountered when expecting a ObjectCollection + private void startCollectionItem(String namespaceURI, String localName, String qName, Attributes attributes, Position position) { + // Finish last list cell, start new one. + if ( ReaderRDFXML_SAX.TRACE ) + trace.println("Generate list cell"); + // Preceding cell in list. + Node previousCollectionNode = collectionNode.node; + Node thisCollectionNode = blankNode(position); + // New cell in list. + // Either link up to the origin or fixup previous cell. + if ( previousCollectionNode == null ) + currentEmitter.emit(currentSubject, currentProperty, thisCollectionNode, position); + else + emit(previousCollectionNode, Nodes.rest, thisCollectionNode, position); + collectionNode.node = thisCollectionNode; - // Start the item. - Node itemSubject = attributesToSubjectNode(attributes, position); - emit(thisCollectionNode, RDF.Nodes.first, itemSubject, position); - startNodeElementWithSubject(itemSubject, namespaceURI, localName, qName, attributes, position); - } + // Start the item. + Node itemSubject = attributesToSubjectNode(attributes, position); + emit(thisCollectionNode, RDF.Nodes.first, itemSubject, position); + startNodeElementWithSubject(itemSubject, namespaceURI, localName, qName, attributes, position); + } private void endCollectionItem(Position position) { if ( ReaderRDFXML_SAX.TRACE ) @@ -952,86 +962,122 @@ class ParserRRX_SAX currentLang = xmlLang; } - // Property attributes. - // The checking is done by the call to hasPropertyAttributes. - private void processPropertyAttributes(Node subject, Attributes attributes, Position position) { - for ( int i = 0 ; i < attributes.getLength() ; i++ ) { - boolean isPropertyAttribute = checkPropertyAttribute(attributes, i, false, position); - if ( ! isPropertyAttribute ) - continue; - propertyAttribute(subject, attributes, i, position); + // Process property attributes - return null for nothing to do. + private Node processPropertyAttributes(Node resourceObj, String qName, Attributes attributes, boolean isPropertyElement, Position position) { + // Subject may not yet be decided. + List<Integer> indexes = gatherPropertyAttributes(attributes, position); + if ( indexes.isEmpty() ) + return null; + if ( isPropertyElement ) { + String parseTypeStr = attributes.getValue(rdfNS, rdfParseType); + if ( parseTypeStr != null ) { + // rdf:parseType found. + throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+qName, position); + } } + + Node innerSubject = (resourceObj==null) ? blankNode(position) : resourceObj; + outputPropertyAttributes(innerSubject, indexes, attributes, position); + return innerSubject; } - // Early abort! But also used to avoid creating a Node for - // processPropertyAttributes which has no work to do. - private boolean hasPropertyAttributes(Attributes attributes, Position position) { - for ( int i = 0 ; i < attributes.getLength() ; i++ ) { - boolean isPropertyAttribute = checkPropertyAttribute(attributes, i, true, position); - if ( ! isPropertyAttribute ) - continue; - return true; + private List<Integer> gatherPropertyAttributes(Attributes attributes, Position position) { + if ( attributes.getLength() == 0 ) + return List.of(); + // Indexes + List<Integer> attributeIdx = new ArrayList<>(attributes.getLength()); + for ( int idx = 0 ; idx < attributes.getLength() ; idx++ ) { + boolean isPropertyAttribute = checkPropertyAttribute(attributes, idx, position); + if ( isPropertyAttribute ) + attributeIdx.add(idx); + } + return attributeIdx; + } + + private void outputPropertyAttributes(Node subject, List<Integer> indexes, Attributes attributes, Position position) { + for ( int index : indexes ) { + String namespaceURI = attributes.getURI(index); + String localName = attributes.getLocalName(index); + String value = attributes.getValue(index); + + if ( rdfNS.equals(namespaceURI) ) { + if ( rdfType.equals(localName) ) { + Node type = iriResolve(value, position); + emit(subject, Nodes.type, type, position); + return; + } + } + Node property = attributeToIRI(namespaceURI, localName, position); + String lex = value; + Node object = literal(lex, currentLang, position); + emit(subject, property, object, position); } - return false; } - /** Return true if this is a property attribute. */ - private boolean checkPropertyAttribute(Attributes attributes, int index, boolean outputWarnings, Position position) { + // 6.1.2 Element Event - attributes + /** Return true if this is an acceptable property attribute. */ + private boolean checkPropertyAttribute(Attributes attributes, int index, Position position) { String namespaceURI = attributes.getURI(index); String localName = attributes.getLocalName(index); String qName = attributes.getQName(index); - if ( StringUtils.isBlank(namespaceURI) ) { - // Note about XML: The empty string namespace does not apply to XML attributes, - // only XML elements. ":attr" is not legal XML. - //RDFXMLparseError("XML attribute '"+qName+"' used for RDF property attribute (no namespace)", position); - - if ( outputWarnings ){ - if ( ! localName.isEmpty() ) // Skip XML namespace declarations. - RDFXMLparseWarning("XML attribute '"+qName+"' used for RDF property attribute - ignored", position); - } - return false; - } if ( isSyntaxAttribute(namespaceURI, localName) ) return false; + // The default namespace (i.e. prefix "") does not apply to attributes. + + // 6.1.2 Element Event - attributes + + if ( coreSyntaxTerm(namespaceURI, localName) ) + return false; + if ( ! allowedPropertyAttributeURIs(namespaceURI, localName) ) - throw RDFXMLparseError("Not allowed as a property attribute: '"+attributes.getQName(index)+"'", position); + throw RDFXMLparseError("Not allowed as a property attribute '"+attributes.getQName(index)+"'", position); - if ( outputWarnings && isNotRecognizedRDFproperty(namespaceURI, localName) ) + if ( isNotRecognizedRDFproperty(namespaceURI, localName) ) RDFXMLparseWarning(qName+" is not a recognized RDF term for a property attribute", position); + // xml:lang, xml:base, xml:space (if these get here). if ( isXMLQName(namespaceURI, localName) ) return false; + // 6.1.2 Element Event + // "All remaining reserved XML Names (see Name in XML 1.0) are now removed from the set." if ( isXMLNamespace(namespaceURI) ) { // Unrecognized qnames in the XMLnamespace are a warning and are ignored. - RDFXMLparseWarning("Unrecognized XML attribute: '"+attributes.getQName(index)+"'", position); + RDFXMLparseWarning("Unrecognized XML attribute '"+qName+"' - ignored", position); return false; } - if ( isXMLNamespaceQName(qName) ) + if ( isXMLNamespaceQName(qName) ) { + // xmlns return false; + } + + if ( StringUtils.isBlank(namespaceURI) ) { + boolean valid = checkPropertyAttributeUnqualifiedTerm(localName, position); + return valid; + } return true; } - /** Output for a property attribute (already checked) */ - private void propertyAttribute(Node subject, Attributes attributes, int index, Position position) { - String namespaceURI = attributes.getURI(index); - String localName = attributes.getLocalName(index); - String value = attributes.getValue(index); - - if ( rdfNS.equals(namespaceURI) ) { - if ( rdfType.equals(localName) ) { - Node type = iriResolve(value, position); - emit(subject, Nodes.type, type, position); - return; + private boolean checkPropertyAttributeUnqualifiedTerm(String localName, Position position) { + if ( allowedUnqualifiedTerm(localName) ) + return true; + if ( localName.length() >= 3 ) { + String chars3 = localName.substring(0, 3); + if ( chars3.equalsIgnoreCase("xml") ) { + // 6.1.2 Element Event + // "all attribute information items with [prefix] property having no value and which + // have [local name] beginning with xml (case independent comparison)" + // Test: unrecognised-xml-attributes/test002.rdf + RDFXMLparseWarning("Unrecognized XML non-namespaced attribute '"+localName+"' - ignored", position); + return false; } } - Node property = qNameToIRI(namespaceURI, localName, position, "property attribute"); - String lex = value; - Node object = literal(lex, currentLang, position); - emit(subject, property, object, position); + // 6.1.4 Attribute Event + // "Other non-namespaced ·local-name· accessor values are forbidden." + throw RDFXMLparseError("Non-namespaced attribute not allowed as a property attribute: '"+localName+"'", position); } /** @@ -1095,13 +1141,6 @@ class ParserRRX_SAX return (s, p, o, loc) -> emitReify(reify, s, p, o, loc); } -// private String xmlBaseStr(Attributes attributes, Position position) { -// String baseStr = attributes.getValue(xmlNS, xmlBaseLN); -// if ( baseStr == null ) -// return null; -// return IRIs.resolve(currentBase, baseStr); -// } - private Node generateLiteral(Position position) { String lex = accCharacters.toString(); if ( datatype != null ) @@ -1271,19 +1310,33 @@ class ParserRRX_SAX // ---- Creating terms. - private Node qNameToIRI(String namespaceURI, String localName, Position position, String usage) { + private Node qNameToIRI(String namespaceURI, String localName, QNameUsage usage, Position position) { if ( StringUtils.isBlank(namespaceURI) ) - throw RDFXMLparseError("Unqualified "+usage+" not allowed: <"+localName+">", position); - String uriStr = qNameToIRI(namespaceURI, localName); + // Default namespace, "" not defined. + throw RDFXMLparseError("Unqualified "+usage.msg+" not allowed: <"+localName+">", position); + String uriStr = strQNameToIRI(namespaceURI, localName); return iri(uriStr, position); } /** This is the RDF rule for creating an IRI from a QName. */ - private String qNameToIRI(String namespaceURI, String localName) { + private String strQNameToIRI(String namespaceURI, String localName) { String iriStr = namespaceURI + localName; return iriStr; } + private Node attributeToIRI(String namespaceURI, String localName, Position position) { + String ns = namespaceURI; + if ( StringUtils.isBlank(namespaceURI) ) { + if ( allowedUnqualifiedTerm(localName) ) + ns = rdfNS; + else + // else rejected in checkPropertyAttribute + throw RDFXMLparseError("Unqualified property attribute not allowed: '"+localName+"'", position); + } + String uriStr = strQNameToIRI(namespaceURI, localName); + return iri(uriStr, position); + } + private Node iri(String uriStr, Position position) { Objects.requireNonNull(uriStr); Objects.requireNonNull(position); @@ -1342,17 +1395,6 @@ class ParserRRX_SAX return parserProfile.createURI(iriStr, line, col); } -// private Node iriDirect(String uriStr, Position position) { -// Objects.requireNonNull(uriStr); -// // No checking. -// return createURIdirect(uriStr, position); -// } -// -// /** Always done without checking. */ -// private Node createURIdirect(String uriStr, Position position) { -// return factory.createURI(uriStr); -// } - private Node blankNode(Position position) { Objects.requireNonNull(position); int line = position.line(); 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 84e97ead58..f0706cdcd4 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 @@ -91,6 +91,13 @@ class ParserRRX_StAX_EV { currentLang = frame.lang; } + /** Mark the usage of a QName */ + private enum QNameUsage { + TypedNodeElement("typed node element"), PropertyElement("property element"); + final String msg; + private QNameUsage(String msg) { this.msg = msg; } + } + // ---- Error handlers private RiotException RDFXMLparseError(String message, XMLEvent event) { @@ -212,19 +219,25 @@ class ParserRRX_StAX_EV { private static Set<QName> $coreSyntaxTerms = Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype); - // 6.2.3 Production syntaxTerms - // coreSyntaxTerms | rdf:Description | rdf:li - private static Set<QName> $syntaxTerms = - Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype, - rdfDescription, rdfContainerItem); +// // 6.2.3 Production syntaxTerms +// // coreSyntaxTerms | rdf:Description | rdf:li +// private static Set<QName> $syntaxTerms = +// Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype, +// rdfDescription, rdfContainerItem); // 6.2.4 Production oldTerms // rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID private static Set<QName> $oldTerms = Set.of(rdfAboutEach, rdfAboutEachPrefix, rdfBagID); - private static Set<QName> disallowedPropertyAttributeTerms = - Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype, - rdfDescription, rdfContainerItem, rdfAboutEach, rdfAboutEachPrefix, rdfBagID); + private static Set<String> $allowedUnqualified = + Set.of(rdfAbout.getLocalPart(), rdfID.getLocalPart(), rdfResource.getLocalPart(), + rdfParseType.getLocalPart(), rdfType.getLocalPart()); + + private boolean coreSyntaxTerm(QName qName) { + if ( ! rdfNS.equals(qName.getNamespaceURI()) ) + return false; + return $coreSyntaxTerms.contains(qName); + } // 6.2.5 Production nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) @@ -270,6 +283,10 @@ class ParserRRX_StAX_EV { return true; } + private static boolean allowedUnqualifiedTerm(String localName) { + return $allowedUnqualified.contains(localName); + } + /** The attributes that guide the RDF/XML parser. */ private static Set<QName> $rdfSyntaxAttributes = Set.of(rdfRDF, rdfAbout, rdfNodeID, rdfID, rdfParseType, rdfDatatype, rdfResource); @@ -374,8 +391,6 @@ class ParserRRX_StAX_EV { // ---- Node Loop - sequence of zero or more RDF/XML node elements. // generic move over multiple elements while ( event != null ) { -// if ( VERBOSE ) -// out.println("-- node loop: "+str(event)); if ( !event.isStartElement() ) break; StartElement startElt = event.asStartElement(); @@ -387,8 +402,6 @@ class ParserRRX_StAX_EV { /** Top level single node element. (no <rdf:RDF>, </rdf:RDF>) */ private XMLEvent nodeElementSingle(XMLEvent event) { -// if ( VERBOSE ) -// out.println("-- single node element: "+str(event)); if ( !event.isStartElement() ) // Protective "not recognized" return event; @@ -448,15 +461,14 @@ class ParserRRX_StAX_EV { RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a type", location); } - Node object = qNameToIRI(qName, location, "typed node element"); + Node object = qNameToIRI(qName, QNameUsage.TypedNodeElement, location); emit(subject, NodeConst.nodeRDFType, object, location); } // Other attributes are properties. // rdf:type is special. - if ( hasAttributeProperties(startElt) ) - processPropertyAttributes(subject, startElt, location); + processPropertyAttributes(subject, startElt, true, location); // Finished with the node start tag. XMLEvent event = nextEventTag(); @@ -471,79 +483,11 @@ class ParserRRX_StAX_EV { return event.asEndElement(); } - // Property attributes. - // The checking is done by the call to hasPropertyAttributes. - private void processPropertyAttributes(Node subject, StartElement startElt, Location location) { - // This both nodes and properties. There is an implicit bnode in when used with properties. - // attributes don't support location :: attribute.getLocation(); - Iterator<Attribute> x = startElt.getAttributes(); - while (x.hasNext()) { - Attribute attribute = x.next(); - boolean isPropertyAttribute = checkPropertyAttribute(attribute.getName(), startElt, false); - if ( ! isPropertyAttribute ) - continue; - QName qName = attribute.getName(); - if ( rdfType.equals(qName) ) { - String iriStr = attribute.getValue(); - Node type = iriResolve(iriStr, location); - emit(subject, RDF.Nodes.type, type, location); - continue; - } - Node property = qNameToIRI(attribute.getName(), location, "property attribute"); - Node object = literal(attribute.getValue(), currentLang, location); - emit(subject, property, object, location); - } - } - - private boolean hasAttributeProperties(StartElement startElt) { - Iterator<Attribute> x = startElt.getAttributes(); - while (x.hasNext()) { - Attribute attribute = x.next(); - boolean isPropertyAttribute = checkPropertyAttribute(attribute.getName(), startElt, true); - if ( ! isPropertyAttribute ) - continue; - return true; - } - return false; - } - - /** Return true if this is a property attribute. */ - private boolean checkPropertyAttribute(QName qName, XMLEvent event, boolean outputWarnings) { - String namespace = qName.getNamespaceURI(); - String localName = qName.getLocalPart(); - if ( namespace == null || namespace.isEmpty() ) { - //RDFXMLparseError("XML attribute '"+localName+"' used for RDF property attribute (no namespace)", event); - if ( outputWarnings ) - RDFXMLparseWarning("XML attribute '"+localName+"' used for RDF property attribute - ignored", event); - return false; - } - if ( isSyntaxAttribute(qName) ) - return false; - - if ( ! allowedPropertyAttributeURIs(qName) ) - throw RDFXMLparseError("Not allowed as a property attribute: '"+str(qName)+"'", event); - - if ( outputWarnings && isNotRecognizedRDFproperty(qName) ) - RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a property attribute", event); - - if ( isXMLQName(qName) ) - return false; - - if ( isXMLNamespace(qName) ) { - // Unrecognized qnames in the XMLnamespace are a warning and are ignored. - RDFXMLparseWarning("Unrecognized XML attribute: '"+str(qName)+"'", event); - return false; - } - return true; - } - // ---- Property elements private XMLEvent propertyElementLoop(Node subject, XMLEvent event) { Counter listElementCounter = new Counter(); while (event != null) { -// if ( VERBOSE ) -// out.println("-- property loop: "+str(event)); if ( ! event.isStartElement() ) break; propertyElement(subject, event.asStartElement(), listElementCounter); @@ -587,7 +531,7 @@ class ParserRRX_StAX_EV { if ( qNameMatches(rdfContainerItem, startElt.getName()) ) property = iriDirect(rdfNS+"_"+Integer.toString(listElementCounter.value++), location); else - property = qNameToIRI(startElt.getName(), location, "property element"); + property = qNameToIRI(startElt.getName(), QNameUsage.PropertyElement, location); Node reify = reifyStatement(startElt); Emitter emitter = (reify==null) ? this::emit : (s,p,o,loc)->emitReify(reify, s, p, o, loc); @@ -617,13 +561,8 @@ class ParserRRX_StAX_EV { if ( objBlanklNodeLabel != null ) resourceObj = blankNode(objBlanklNodeLabel, location); - if ( hasAttributeProperties(startElt) ) { - if ( parseType != parseTypePlain ) { - // rdf:parseType found. - throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+startElt.getName(), startElt); - } - Node innerSubject = (resourceObj==null) ? blankNode(location) : resourceObj; - processPropertyAttributes(innerSubject, startElt, location); + Node innerSubject = processPropertyAttributes(resourceObj, startElt, true, location); + if ( resourceObj == null && innerSubject != null ) { emitter.emit(subject, property, innerSubject, location); XMLEvent event = nextEventAny(); if ( !event.isEndElement() ) @@ -731,6 +670,108 @@ class ParserRRX_StAX_EV { return event; } + private Node processPropertyAttributes(Node resourceObj, StartElement startElt, boolean isPropertyElement, Location location) { + // Subject may not yet be decided. + List<Attribute> propertyAttributes = gatherPropertyAttributes(startElt, location); + if ( propertyAttributes.isEmpty() ) + return null; + if ( isPropertyElement ) { + String parseTypeStr = objectParseType(startElt); + if ( parseTypeStr != parseTypePlain ) { + // rdf:parseType found. + throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+str(startElt.getName()), location); + } + } + + Node innerSubject = (resourceObj==null) ? blankNode(location) : resourceObj; + outputPropertyAttributes(innerSubject, propertyAttributes, location); + return innerSubject; + } + + private List<Attribute> gatherPropertyAttributes(StartElement startElt, Location location) { + Iterator<Attribute> x = startElt.getAttributes(); + if ( ! x.hasNext() ) + return List.of(); + List<Attribute> propertyAttributes = new ArrayList<>(); + while(x.hasNext()) { + Attribute attribute = x.next(); + boolean isPropertyAttribute = checkPropertyAttribute(attribute.getName(), location); + if ( isPropertyAttribute ) + propertyAttributes.add(attribute); + } + return propertyAttributes; + } + + private void outputPropertyAttributes(Node subject, List<Attribute> propertyAttributes, Location location) { + for ( Attribute attribute : propertyAttributes ) { + QName qName = attribute.getName(); + if ( rdfType.equals(qName) ) { + String iriStr = attribute.getValue(); + Node type = iriResolve(iriStr, location); + emit(subject, RDF.Nodes.type, type, location); + return; + } + Node property = attributeToIRI(qName, location); + String lexicalForm = attribute.getValue(); + Node object = literal(lexicalForm, currentLang, location); + emit(subject, property, object, location); + } + } + + /** Return true if this is a property attribute. */ + private boolean checkPropertyAttribute(QName qName, Location location) { + if ( isSyntaxAttribute(qName) ) + return false; + + // The default namespace (i.e. prefix "") does not apply to attributes. + + // 6.1.2 Element Event - attributes + + if (coreSyntaxTerm(qName) ) + return false; + + if ( ! allowedPropertyAttributeURIs(qName) ) + throw RDFXMLparseError("Not allowed as a property attribute: '"+str(qName)+"'", location); + + if ( isNotRecognizedRDFproperty(qName) ) + RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a property attribute", location); + + if ( isXMLQName(qName) ) + return false; + + if ( isXMLNamespace(qName) ) { + // Unrecognized qnames in the XMLnamespace are a warning and are ignored. + RDFXMLparseWarning("Unrecognized XML attribute: '"+str(qName)+"'", location); + return false; + } + + if ( StringUtils.isBlank(qName.getNamespaceURI()) ) { + String localName = qName.getLocalPart(); + boolean valid = checkPropertyAttributeUnqualifiedTerm(localName, location); + return valid; + } + return true; + } + + private boolean checkPropertyAttributeUnqualifiedTerm(String localName, Location location) { + if ( allowedUnqualifiedTerm(localName) ) + return true; + if ( localName.length() >= 3 ) { + String chars3 = localName.substring(0, 3); + if ( chars3.equalsIgnoreCase("xml") ) { + // 6.1.2 Element Event + // "all attribute information items with [prefix] property having no value and which + // have [local name] beginning with xml (case independent comparison)" + // Test: unrecognised-xml-attributes/test002.rdf + RDFXMLparseWarning("Unrecognized XML non-namespaced attribute '"+localName+"' - ignored", location); + return false; + } + } + // 6.1.4 Attribute Event + // "Other non-namespaced ·local-name· accessor values are forbidden." + throw RDFXMLparseError("Non-namespaced attribute not allowed as a property attribute: '"+localName+"'", location); + } + private String objectParseType(StartElement startElt) { String parseTypeStr = attribute(startElt, rdfParseType); return ( parseTypeStr != null ) ? parseTypeStr : parseTypePlain; @@ -1126,18 +1167,32 @@ class ParserRRX_StAX_EV { parserProfile.setBaseIRI(n.getURI()); } - private Node qNameToIRI(QName qName, Location location, String usage) { + private Node qNameToIRI(QName qName, QNameUsage usage, Location location) { if ( StringUtils.isBlank(qName.getNamespaceURI()) ) - throw RDFXMLparseError("Unqualified "+usage+" not allowed: <"+qName.getLocalPart()+">", location); - String uriStr = qNameToIRI(qName); + throw RDFXMLparseError("Unqualified "+usage.msg+" not allowed: <"+qName.getLocalPart()+">", location); + String uriStr = strQNameToIRI(qName); return iriDirect(uriStr, location); } /** This is the RDF rule for creating an IRI from a QName. */ - private String qNameToIRI(QName qName) { + private String strQNameToIRI(QName qName) { return qName.getNamespaceURI()+qName.getLocalPart(); } + private Node attributeToIRI(QName qName, Location location) { + String namespaceURI = qName.getNamespaceURI(); + String localName = qName.getLocalPart(); + if ( StringUtils.isBlank(namespaceURI) ) { + if ( allowedUnqualifiedTerm(localName) ) + namespaceURI = rdfNS; + else + // else rejected in checkPropertyAttribute + throw RDFXMLparseError("Unqualified property attribute not allowed: '"+localName+"'", location); + } + String uriStr = namespaceURI+localName; + return iriDirect(uriStr, location); + } + // ---- Reading XMLEvents /** XML parsing error. */ 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 a8eaa8402e..d6df70f8af 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 @@ -94,6 +94,13 @@ class ParserRRX_StAX_SR { currentLang = frame.lang; } + /** Mark the usage of a QName */ + private enum QNameUsage { + TypedNodeElement("typed node element"), PropertyElement("property element"); + final String msg; + private QNameUsage(String msg) { this.msg = msg; } + } + // ---- Error handlers private RiotException RDFXMLparseError(String message) { @@ -222,9 +229,16 @@ class ParserRRX_StAX_SR { // rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID private static Set<QName> $oldTerms = Set.of(rdfAboutEach, rdfAboutEachPrefix, rdfBagID); - private static Set<QName> disallowedPropertyAttributeTerms = - Set.of(rdfRDF, rdfID, rdfAbout, rdfParseType, rdfResource, rdfNodeID, rdfDatatype, - rdfDescription, rdfContainerItem, rdfAboutEach, rdfAboutEachPrefix, rdfBagID); + private static Set<String> $allowedUnqualified = + Set.of(rdfAbout.getLocalPart(), rdfID.getLocalPart(), rdfResource.getLocalPart(), + rdfParseType.getLocalPart(), rdfType.getLocalPart()); + + private boolean coreSyntaxTerm(QName qName) { + if ( ! rdfNS.equals(qName.getNamespaceURI()) ) + return false; + return $coreSyntaxTerms.contains(qName); + } + // 6.2.5 Production nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) @@ -270,6 +284,10 @@ class ParserRRX_StAX_SR { return true; } + private static boolean allowedUnqualifiedTerm(String localName) { + return $allowedUnqualified.contains(localName); + } + /** The attributes that guide the RDF/XML parser. */ private static Set<QName> $rdfSyntaxAttributes = Set.of(rdfRDF, rdfAbout, rdfNodeID, rdfID, rdfParseType, rdfDatatype, rdfResource); @@ -368,8 +386,6 @@ class ParserRRX_StAX_SR { /** Top level single node element. (no <rdf:RDF>, </rdf:RDF>) */ private void nodeElementSingle(int eventType) { -// if ( VERBOSE ) -// out.println("-- single node element: "+str(event)); if ( ! lookingAt(eventType, START_ELEMENT) ) return ; nodeElement(); @@ -428,15 +444,11 @@ class ParserRRX_StAX_SR { RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a type"); } - Node object = qNameToIRI(qName, location, "type node element"); + Node object = qNameToIRI(qName, QNameUsage.TypedNodeElement, location); emit(subject, NodeConst.nodeRDFType, object, location); } - // Other attributes are properties. - // rdf:type is special. - - if ( hasAttributeProperties() ) - processPropertyAttributes(subject, location); + processPropertyAttributes(subject, qName, false, location); // Finished with the node start tag. int event = nextEventTag(); @@ -446,80 +458,11 @@ class ParserRRX_StAX_SR { throw RDFXMLparseError("Expected end element for "+qName()); } - // Property attributes. - // The checking is done by the call to hasPropertyAttributes. - private void processPropertyAttributes(Node subject, Location location) { - // This both nodes and properties. There is an implicit bnode in when used with properties. - int N = xmlSource.getAttributeCount(); - for ( int i = 0 ; i < N ; i++ ) { - QName qName = xmlSource.getAttributeName(i); - boolean isPropertyAttribute = checkPropertyAttribute(qName, false); - if ( ! isPropertyAttribute ) - continue; - if ( rdfType.equals(qName) ) { - String iriStr = xmlSource.getAttributeValue(i); - Node type = iriResolve(iriStr, location); - emit(subject, RDF.Nodes.type, type, location); - continue; - } - Node property = qNameToIRI(qName, location, "property attribute"); - String lexicalForm = xmlSource.getAttributeValue(i); - Node object = literal(lexicalForm, currentLang, location); - emit(subject, property, object, location); - } - } - - private boolean hasAttributeProperties() { - int N = xmlSource.getAttributeCount(); - for ( int i = 0 ; i < N ; i++ ) { - QName qName = xmlSource.getAttributeName(i); - boolean isPropertyAttribute = checkPropertyAttribute(qName, true); - if ( ! isPropertyAttribute ) - continue; - return true; - } - return false; - } - - /** - * Return true if this is a property attribute. - * @param qName - */ - private boolean checkPropertyAttribute(QName qName, boolean outputWarnings) { - if ( StringUtils.isBlank(qName.getNamespaceURI()) ) { - //RDFXMLparseError("XML attribute '"+localName+"' used for RDF property attribute (no namespace)", event); - if ( outputWarnings ) - RDFXMLparseWarning("XML attribute '"+qName.getLocalPart()+"' used for RDF property attribute - ignored"); - return false; - } - - if ( isSyntaxAttribute(qName) ) - return false; - - if ( ! allowedPropertyAttributeURIs(qName) ) - throw RDFXMLparseError("Not allowed as a property attribute: '"+str(qName)+"'"); - - if ( outputWarnings && isNotRecognizedRDFproperty(qName) ) - RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a property attribute"); - - if ( isXMLQName(qName) ) - return false; - - if ( isXMLNamespace(qName) ) { - // Unrecognized qnames in the XMLnamespace are a warning and are ignored. - RDFXMLparseWarning("Unrecognized XML attribute: '"+str(qName)+"'"); - return false; - } - return true; - } - // ---- Property elements private int propertyElementlLoop(Node subject, int event) { Counter listElementCounter = new Counter(); while (true) { -// if ( VERBOSE ) -// out.println("-- property loop: "+str(event)); if ( ! lookingAt(event, START_ELEMENT) ) break; propertyElement(subject, listElementCounter, location()); @@ -562,7 +505,7 @@ class ParserRRX_StAX_SR { if ( qNameMatches(rdfContainerItem, qName) ) property = iriDirect(rdfNS+"_"+Integer.toString(listElementCounter.value++), location()); else - property = qNameToIRI(qName, location, "property element"); + property = qNameToIRI(qName, QNameUsage.PropertyElement, location); Node reify = reifyStatement(location); Emitter emitter = (reify==null) ? this::emit : (s,p,o,loc)->emitReify(reify, s, p, o, loc); @@ -592,13 +535,8 @@ class ParserRRX_StAX_SR { if ( objBlanklNodeLabel != null ) resourceObj = blankNode(objBlanklNodeLabel, location); - if ( hasAttributeProperties() ) { - if ( parseType != parseTypePlain ) { - // rdf:parseType found. - throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+str(qName)); - } - Node innerSubject = (resourceObj==null) ? blankNode(location) : resourceObj; - processPropertyAttributes(innerSubject, location); + Node innerSubject = processPropertyAttributes(resourceObj, qName, true, location); + if ( resourceObj == null && innerSubject != null ) { emitter.emit(subject, property, innerSubject, location); int event = nextEventAny(); if ( ! lookingAt(event, END_ELEMENT) ) @@ -704,6 +642,109 @@ class ParserRRX_StAX_SR { return event; } + private Node processPropertyAttributes(Node resourceObj, QName qName, boolean isPropertyElement, Location location) { + // Subject may not yet be decided. + List<Integer> indexes = gatherPropertyAttributes(location); + if ( indexes.isEmpty() ) + return null; + if ( isPropertyElement ) { + String parseTypeStr = objectParseType(); + if ( parseTypeStr != parseTypePlain ) { + // rdf:parseType found. + throw RDFXMLparseError("The attribute rdf:parseType is not permitted with property attributes on a property element: "+str(qName), location); + } + } + + Node innerSubject = (resourceObj==null) ? blankNode(location) : resourceObj; + outputPropertyAttributes(innerSubject, indexes, location); + return innerSubject; + } + + private List<Integer> gatherPropertyAttributes(Location location) { + int N = xmlSource.getAttributeCount(); + if ( N == 0 ) + return List.of(); + List<Integer> attributeIdx = new ArrayList<>(N); + for ( int idx = 0 ; idx < N ; idx++ ) { + QName qName = xmlSource.getAttributeName(idx); + boolean isPropertyAttribute = checkPropertyAttribute(qName, location); + if ( isPropertyAttribute ) + attributeIdx.add(idx); + } + return attributeIdx; + } + + private void outputPropertyAttributes(Node subject, List<Integer> indexes, Location location) { + for ( int index : indexes ) { + QName qName = xmlSource.getAttributeName(index); + if ( rdfType.equals(qName) ) { + String iriStr = xmlSource.getAttributeValue(index); + Node type = iriResolve(iriStr, location); + emit(subject, RDF.Nodes.type, type, location); + return; + } + Node property = attributeToIRI(qName, location); + String lexicalForm = xmlSource.getAttributeValue(index); + Node object = literal(lexicalForm, currentLang, location); + emit(subject, property, object, location); + } + } + + /** Return true if this is a property attribute. */ + private boolean checkPropertyAttribute(QName qName, Location location) { + if ( isSyntaxAttribute(qName) ) + return false; + + // The default namespace (i.e. prefix "") does not apply to attributes. + + // 6.1.2 Element Event - attributes + + if (coreSyntaxTerm(qName) ) + return false; + + if ( ! allowedPropertyAttributeURIs(qName) ) + throw RDFXMLparseError("Not allowed as a property attribute: '"+str(qName)+"'"); + + if ( isNotRecognizedRDFproperty(qName) ) + RDFXMLparseWarning(str(qName)+" is not a recognized RDF term for a property attribute"); + + if ( isXMLQName(qName) ) + return false; + + if ( isXMLNamespace(qName) ) { + // Unrecognized qnames in the XMLnamespace are a warning and are ignored. + RDFXMLparseWarning("Unrecognized XML attribute: '"+str(qName)+"'"); + return false; + } + + if ( StringUtils.isBlank(qName.getNamespaceURI()) ) { + String localName = qName.getLocalPart(); + boolean valid = checkPropertyAttributeUnqualifiedTerm(localName, location); + return valid; + } + return true; + } + + private boolean checkPropertyAttributeUnqualifiedTerm(String localName, Location location) { + if ( allowedUnqualifiedTerm(localName) ) + return true; + if ( localName.length() >= 3 ) { + String chars3 = localName.substring(0, 3); + if ( chars3.equalsIgnoreCase("xml") ) { + // 6.1.2 Element Event + // "all attribute information items with [prefix] property having no value and which + // have [local name] beginning with xml (case independent comparison)" + // Test: unrecognised-xml-attributes/test002.rdf + RDFXMLparseWarning("Unrecognized XML non-namespaced attribute '"+localName+"' - ignored", location); + return false; + } + } + // 6.1.4 Attribute Event + // "Other non-namespaced ·local-name· accessor values are forbidden." + throw RDFXMLparseError("Non-namespaced attribute not allowed as a property attribute: '"+localName+"'", location); + } + + private String objectParseType() { String parseTypeStr = attribute(rdfParseType); return ( parseTypeStr != null ) ? parseTypeStr : parseTypePlain; @@ -1082,9 +1123,9 @@ class ParserRRX_StAX_SR { } /** This is the RDF rule for creating an IRI from a QName. */ - private Node qNameToIRI(QName qName, Location location, String usage) { + private Node qNameToIRI(QName qName, QNameUsage usage, Location location) { if ( StringUtils.isBlank(qName.getNamespaceURI()) ) - throw RDFXMLparseError("Unqualified "+usage+" not allowed: <"+qName.getLocalPart()+">", location); + throw RDFXMLparseError("Unqualified "+usage.msg+" not allowed: <"+qName.getLocalPart()+">", location); String uriStr = strQNameToIRI(qName); return iriDirect(uriStr, location); } @@ -1094,6 +1135,20 @@ class ParserRRX_StAX_SR { return qName.getNamespaceURI()+qName.getLocalPart(); } + private Node attributeToIRI(QName qName, Location location) { + String namespaceURI = qName.getNamespaceURI(); + String localName = qName.getLocalPart(); + if ( StringUtils.isBlank(namespaceURI) ) { + if ( allowedUnqualifiedTerm(localName) ) + namespaceURI = rdfNS; + else + // else rejected in checkPropertyAttribute + throw RDFXMLparseError("Unqualified property attribute not allowed: '"+localName+"'", location); + } + String uriStr = namespaceURI+localName; + return iriDirect(uriStr, location); + } + // ---- Reading XML /** Get the string value for an attribute by QName */ diff --git a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/RunTestRDFXML.java b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/RunTestRDFXML.java index 2f12d64e41..a66196e947 100644 --- a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/RunTestRDFXML.java +++ b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/rrx/RunTestRDFXML.java @@ -372,8 +372,8 @@ public class RunTestRDFXML { checkErrorHandler(testLabel, expectedErrorHandler, errorHandlerTest); return; } catch(RiotException ex) { - output.println("## "+testLabel); - ex.printStackTrace(); +// output.println("## "+testLabel); +// ex.printStackTrace(); fail("Unexpected parse error: "+ex.getMessage()); } } @@ -407,7 +407,7 @@ public class RunTestRDFXML { ErrorHandlerCollector actualErrorHandler = new ErrorHandlerCollector(); assertThrows(RiotException.class, ()->{ parseFile(testSubjectFactory, actualErrorHandler, filename); - output.printf("## Expected RiotExpection : %-4s : %s : %s", subjectLabel, testLabel, filename); + output.printf("## Expected RiotExpection : %-4s : %s : %s\n", subjectLabel, testLabel, filename); }); if ( expectedErrorHandler != null ) 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 44235fb102..887ae1dfea 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 @@ -72,8 +72,6 @@ public class TestRRX { this.lang = lang; } - // XXX Track files! - private static Set<String> processedFiles = new HashSet<>(); private void trackFilename(String filename) { processedFiles.add(filename); @@ -171,6 +169,26 @@ public class TestRRX { checkForError("bad-unqualified-property.rdf", false); } + @Test public void bad_unqualified_attribute1() { + checkForError("bad-unqualified-attribute1.rdf", false); + } + + @Test public void bad_unqualified_attribute2() { + checkForError("bad-unqualified-attribute2.rdf", false); + } + + @Test public void bad_unqualified_attribute3() { + checkForError("bad-unqualified-attribute3.rdf", false); + } + + @Test public void bad_unqualified_attribute4() { + warningTest("bad-unqualified-attribute4.rdf", 1); + } + + @Test public void bad_unqualified_attribute5() { + warningTest("bad-unqualified-attribute5.rdf", 1); + } + @Test public void bad_unqualified_class() { checkForError("bad-unqualified-class.rdf", false); } diff --git a/jena-arq/testing/RIOT/rrx-files/bad-rdf-id-node.rdf b/jena-arq/testing/RIOT/rrx-files/bad-rdf-id-node.rdf index db19f039ff..071ac9b2ed 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-rdf-id-node.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-rdf-id-node.rdf @@ -11,4 +11,4 @@ <rdf:Description rdf:about="http://example/s" rdf:ID="foo"> <ex:property>text</ex:property> </rdf:Description> -</rdf:RDF> \ No newline at end of file +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-rdf-resource-node.rdf b/jena-arq/testing/RIOT/rrx-files/bad-rdf-resource-node.rdf index ee3bbab131..b29db442a2 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-rdf-resource-node.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-rdf-resource-node.rdf @@ -11,4 +11,4 @@ <rdf:Description rdf:about="http://example/s" rdf:resource="http://example/foo"> <ex:property>text</ex:property> </rdf:Description> -</rdf:RDF> \ No newline at end of file +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute1.rdf b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute1.rdf index dd1db8e7c3..5957021ab9 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute1.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute1.rdf @@ -3,4 +3,4 @@ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description name="NAME"/> -</rdf:RDF> \ No newline at end of file +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf index 21940b5d63..9a0bf21edf 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf @@ -6,4 +6,4 @@ xmlns="http://example/"> <!-- qnames do not apply to attributes unless ex:attr --> <rdf:Description name="NAME"/> -</rdf:RDF> \ No newline at end of file +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute3.rdf similarity index 66% copy from jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf copy to jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute3.rdf index 21940b5d63..be9dbd3fce 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute3.rdf @@ -4,6 +4,6 @@ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://example/"> - <!-- qnames do not apply to attributes unless ex:attr --> - <rdf:Description name="NAME"/> -</rdf:RDF> \ No newline at end of file + <!-- less that 3 characters attribute --> + <rdf:Description n="NAME"/> +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute4.rdf similarity index 66% copy from jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf copy to jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute4.rdf index 21940b5d63..400c1db00e 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute4.rdf @@ -4,6 +4,6 @@ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://example/"> - <!-- qnames do not apply to attributes unless ex:attr --> - <rdf:Description name="NAME"/> -</rdf:RDF> \ No newline at end of file + <!-- xml : warning --> + <rdf:Description xml:n="NAME"/> +</rdf:RDF> diff --git a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute5.rdf similarity index 63% copy from jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf copy to jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute5.rdf index 21940b5d63..70da6bddb0 100644 --- a/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute2.rdf +++ b/jena-arq/testing/RIOT/rrx-files/bad-unqualified-attribute5.rdf @@ -4,6 +4,6 @@ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://example/"> - <!-- qnames do not apply to attributes unless ex:attr --> - <rdf:Description name="NAME"/> -</rdf:RDF> \ No newline at end of file + <!-- RDF/XML calls out plain attributes startin "xml" : warning --> + <rdf:Description XmLname="NAME"/> +</rdf:RDF>
