/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.rdfxml.xmlinput.states; import java.util.ArrayList; import org.apache.jena.rdfxml.xmlinput.impl.* ; import org.xml.sax.Attributes; import org.xml.sax.SAXParseException; public class WantPropertyElement extends Frame implements WantsObjectFrameI, HasSubjectFrameI { int liCounter = 1; ANode predicate; ANode object; ANode reify; boolean objectIsBlank = false; public WantPropertyElement(HasSubjectFrameI s, AbsXMLContext x) { super(s, x); } // These three are used as bitfields static final private int TYPEDLITERAL = 1; static final private int EMPTYWITHOBJ = 2; static final private int PARSETYPE = 4; @Override public FrameI startElement(String uri, String localName, String rawName, Attributes atts) throws SAXParseException { clearObject(); if (nonWhiteMsgGiven) taint.isTainted(); nonWhiteMsgGiven = false; if (uri==null || uri.equals("")) { warning(WARN_UNQUALIFIED_ELEMENT,"Unqualified property elements are not allowed. Treated as a relative URI."); } ElementLexer el = new ElementLexer(taint, this, uri, localName, rawName, E_LI, CoreAndOldTerms | E_DESCRIPTION, false); // if (el.badMatch) // warning(ERR_SYNTAX_ERROR,"bad use of " + rawName); predicate = el.goodMatch ? (AResourceInternal) rdf_n(liCounter++) : URIReference.fromQName(this, uri, localName); if (taint.isTainted()) predicate.taint(); taint = new TaintImpl(); AttributeLexer ap = new AttributeLexer(this, // xml: A_XMLLANG | A_XMLBASE | A_XML_OTHER // legal rdf: | A_DATATYPE | A_ID | A_NODEID | A_PARSETYPE | A_RESOURCE | A_TYPE, // bad rdf: A_BADATTRS); int cnt = ap.processSpecials(taint, atts); // These three states are intended as mutually // incompatible, but all three can occur // together. Any two of the three, or all // three is a syntax errror. // Having none of these is legal. final int nextStateCode = (ap.datatype == null ? 0 : TYPEDLITERAL) | (ap.parseType == null ? 0 : PARSETYPE) | (mustBeEmpty(ap, atts, cnt) ? EMPTYWITHOBJ : 0); if (this.badStateCode(nextStateCode)) { warning(errorNumber(nextStateCode), descriptionOfCases(ap, nextStateCode, propertyAttributeDescription(atts, ap, cnt))); } AbsXMLContext x = ap.xml(xml); reify = ap.id == null ? null : URIReference.fromID(this, x, ap.id); if (taint.isTainted()) predicate.taint(); if (mustBeEmpty(ap, atts, cnt)) { if (ap.nodeID != null) { object = new ARPResource(arp, ap.nodeID); checkNodeID_XMLName(object, ap.nodeID); objectIsBlank = true; } if (ap.resource != null) { if (object != null) { if (!badStateCode(nextStateCode)) // otherwise warning already given warning(ERR_SYNTAX_ERROR, "On a property element, only one of the attributes rdf:nodeID or rdf:resource is permitted."); } else object = URIReference.resolve(this, x, ap.resource); } if (object == null) { object = new ARPResource(arp); objectIsBlank = true; } if (taint.isTainted()) object.taint(); processPropertyAttributes(ap, atts, x); } FrameI nextFrame = nextFrame(atts, ap, cnt, nextStateCode, x); if (object != null) { if (taint.isTainted()) object.taint(); theObject(object); } if (taint.isTainted()) predicate.taint(); return nextFrame; } private boolean mustBeEmpty(AttributeLexer ap, Attributes atts, int cnt) { return cnt < atts.getLength() || ap.type != null || ap.nodeID != null || ap.resource != null; } private FrameI nextFrame(Attributes atts, AttributeLexer ap, int cnt, int nextStateCode, AbsXMLContext x) throws SAXParseException { switch (nextStateCode) { case 0: return new WantLiteralValueOrDescription(this, x); case PARSETYPE | TYPEDLITERAL: case PARSETYPE | TYPEDLITERAL | EMPTYWITHOBJ: case PARSETYPE | EMPTYWITHOBJ: case PARSETYPE: return withParsetype(ap.parseType, x); case TYPEDLITERAL | EMPTYWITHOBJ: case TYPEDLITERAL: return new WantTypedLiteral(this, ap.datatype, x); case EMPTYWITHOBJ: return new WantEmpty(this, x); } throw new IllegalStateException("impossible"); } private FrameI withParsetype(String pt, AbsXMLContext x) throws SAXParseException { if (pt.equals("Collection")) { return new RDFCollection(this, x); } if (pt.equals("Resource")) { if (object == null) { // in some error cases the object has already been set. object = new ARPResource(arp); objectIsBlank = true; } return new WantPropertyElement(this, x); } if (!pt.equals("Literal")) { warning(WARN_UNKNOWN_PARSETYPE, "Unknown rdf:parseType: '" + pt + "' (treated as 'Literal'."); } return new OuterXMLLiteral(this, x, pt); } @Override String suggestParsetypeLiteral() { return (getParent() instanceof WantTopLevelDescription) ? "" : super .suggestParsetypeLiteral(); } @Override public void aPredAndObj(ANode p, ANode o) { triple(object, p, o); } @Override public void makeSubjectReificationWith(ANode r) { triple(r, RDF_SUBJECT, object); } @Override public void theObject(ANode o) { HasSubjectFrameI p = (HasSubjectFrameI) getParent(); p.aPredAndObj(predicate, o); if (reify != null) { triple(reify, RDF_TYPE, RDF_STATEMENT); triple(reify, RDF_OBJECT, o); triple(reify, RDF_PREDICATE, predicate); p.makeSubjectReificationWith(reify); } } @Override public void endElement() { clearObject(); } @Override public void abort() { clearObject(); } private void clearObject() { if (objectIsBlank) arp.endLocalScope(object); objectIsBlank = false; object = null; } static private URIReference _rdf_n[] = new URIReference[0]; static private URIReference rdf_n(int i) { if (i >= _rdf_n.length) { int newLength = (i + 10) * 3 / 2; URIReference new_rdf_n[] = new URIReference[newLength]; System.arraycopy(_rdf_n, 0, new_rdf_n, 0, _rdf_n.length); for (int j = _rdf_n.length; j < newLength; j++) { new_rdf_n[j] = URIReference.createNoChecks(rdfns + "_" + j); } _rdf_n = new_rdf_n; } return _rdf_n[i]; } /*************************************************************************** * * ERROR HANDLING CODE * **************************************************************************/ // Error detection private boolean badStateCode(int nextStateCode) { switch (nextStateCode) { case PARSETYPE | TYPEDLITERAL: case PARSETYPE | TYPEDLITERAL | EMPTYWITHOBJ: case PARSETYPE | EMPTYWITHOBJ: case TYPEDLITERAL | EMPTYWITHOBJ: return true; case 0: case PARSETYPE: case TYPEDLITERAL: case EMPTYWITHOBJ: return false; } throw new IllegalStateException("impossible"); } // Error classification private int errorNumber(int nextStateCode) { // TODO: not for 2.3. refine this error code. return ERR_SYNTAX_ERROR; } /*************************************************************************** * * ERROR MESSAGES * **************************************************************************/ private String descriptionOfCases(AttributeLexer ap, int nextStateCode, String propAttrs) { return ((propAttrs == null && ap.type == null) || (ap.nodeID == null && ap.resource == null && ap.type == null) || (ap.nodeID == null && ap.resource == null && propAttrs == null)) ? pairwiseIncompatibleErrorMessage( nextStateCode, ap, propAttrs) : complicatedErrorMessage(nextStateCode, ap, propAttrs); } private String pairwiseIncompatibleErrorMessage(int nextStateCode, AttributeLexer ap, String propAttrs) { ArrayList<String> cases = new ArrayList<>(); if ((nextStateCode & PARSETYPE) != 0) cases.add("rdf:parseType"); if ((nextStateCode & TYPEDLITERAL) != 0) cases.add("rdf:datatype"); if (ap.nodeID != null) cases.add("rdf:nodeID"); if (ap.resource != null) cases.add("rdf:resource"); if (ap.type != null) cases.add("rdf:type"); if (cases.size() == 1) { if (propAttrs == null) throw new IllegalStateException("Shouldn't happen."); return "The attribute " + cases.get(0) + " is not permitted with " + propAttrs + " on a property element."; } String rslt = "On a property element, only one of the "; if (propAttrs == null) rslt += "attributes "; for (int i = 0; i < cases.size(); i++) { rslt += cases.get(i); switch (cases.size() - i) { case 1: break; case 2: rslt += " or "; break; default: rslt += ", "; break; } } if (propAttrs != null) { rslt += " attributes or " + propAttrs; } rslt += " is permitted."; return rslt; } private String complicatedErrorMessage(int nextStateCode, AttributeLexer ap, String propAttrs) { String subjectIs; if (ap.nodeID == null && ap.resource == null && (ap.type == null || propAttrs == null)) throw new IllegalStateException("precondition failed."); switch (nextStateCode & (TYPEDLITERAL | PARSETYPE)) { case TYPEDLITERAL | PARSETYPE: subjectIs = "the mutually incompatible attributes rdf:datatype and rdf:parseType are"; break; case TYPEDLITERAL: subjectIs = "the attribute rdf:datatype is"; break; case PARSETYPE: subjectIs = "the attribute rdf:parseType is"; break; default: throw new IllegalStateException("precondition failed"); } String nodeIDResource = null; if (ap.nodeID != null && ap.resource != null) { nodeIDResource = "the mutually incompatible attributes rdf:nodeID and rdf:resource"; } else if (ap.nodeID != null) { nodeIDResource = "the attribute rdf:nodeID"; } else if (ap.resource != null) { nodeIDResource = "the attribute rdf:resource"; } int otherAttCount = nodeIDResource == null ? 0 : 1; String otherAtts; if (ap.type != null) otherAttCount++; if (propAttrs != null) otherAttCount++; if (otherAttCount < 2) throw new IllegalStateException("logic error"); otherAtts = otherAttCount == 2 ? "both " : "each of "; if (ap.type != null && propAttrs != null) { if (nodeIDResource == null) otherAtts += "the attribute rdf:type and the " + propAttrs; else otherAtts += "the attribute rdf:type, the " + propAttrs; } else if (ap.type != null) { otherAtts += "the attribute rdf:type"; } else { otherAtts = "the " + propAttrs; } if (nodeIDResource != null) otherAtts += " and "+nodeIDResource; return "On a property element, " + subjectIs + " incompatible with " + otherAtts +"."; } private String propertyAttributeDescription(Attributes atts, AttributeLexer ap, int cnt) { String propAttrs = ""; int propAttrCount = atts.getLength() - cnt; int found = 0; if (propAttrCount == 0) return null; switch (propAttrCount) { case 0: break; case 1: case 2: case 3: for (int i = 0; i < atts.getLength(); i++) if (!ap.done(i)) { propAttrs += atts.getQName(i); found++; switch (propAttrCount - found) { case 0: break; case 1: propAttrs += " and "; break; default: propAttrs += ", "; } } break; default: if (propAttrCount < 0) throw new IllegalStateException("Shouldn't happen."); for (int i = 0; i < atts.getLength(); i++) if (!ap.done(i)) { found++; switch (found) { case 1: propAttrs += atts.getQName(i) + ", "; break; case 2: propAttrs += atts.getQName(i) + ", ..."; break; default: // ignore } } } return "property attributes (" + propAttrs + ")"; } }