| /* |
| * 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.sparql.resultset ; |
| |
| import java.io.IOException ; |
| import java.io.InputStream ; |
| import java.io.Reader ; |
| import java.util.ArrayList ; |
| import java.util.List ; |
| |
| import org.apache.jena.atlas.logging.Log ; |
| import org.apache.jena.datatypes.RDFDatatype ; |
| import org.apache.jena.datatypes.TypeMapper ; |
| import org.apache.jena.graph.Node ; |
| import org.apache.jena.graph.NodeFactory ; |
| import org.apache.jena.rdf.model.Model ; |
| import org.apache.jena.riot.lang.LabelToNode; |
| import org.apache.jena.riot.resultset.rw.XMLResults; |
| import org.apache.jena.riot.system.SyntaxLabels; |
| import org.apache.jena.sparql.core.Var ; |
| import org.apache.jena.sparql.engine.ResultSetStream ; |
| import org.apache.jena.sparql.engine.binding.Binding ; |
| import org.apache.jena.sparql.engine.binding.BindingFactory ; |
| import org.apache.jena.sparql.engine.binding.BindingMap ; |
| import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper ; |
| import org.apache.jena.sparql.graph.GraphFactory ; |
| import org.apache.jena.sparql.util.FmtUtils ; |
| import org.apache.jena.vocabulary.RDF ; |
| import org.xml.sax.* ; |
| import org.xml.sax.helpers.XMLReaderFactory ; |
| |
| /** Code that reads an XML Result Set and builds the ARQ structure for the same. */ |
| // UNUSED |
| class XMLInputSAX extends SPARQLResult { |
| // See also XMLInputStAX, which is preferred. |
| // SAX is not a streaming API - the SAX handler is called as fast as the |
| // parser wants to call it, so the parser is calling for all the XML |
| // and we have to build an in-memory structure (or the client application |
| // would need to be inside the code path of the SAX handler). |
| |
| public XMLInputSAX(InputStream in, Model model) { |
| worker(new InputSource(in), model) ; |
| } |
| |
| public XMLInputSAX(Reader in, Model model) { |
| worker(new InputSource(in), model) ; |
| } |
| |
| public XMLInputSAX(String str, Model model) { |
| worker(new InputSource(str), model) ; |
| } |
| |
| private void worker(InputSource in, Model model) { |
| if ( model == null ) |
| model = GraphFactory.makeJenaDefaultModel() ; |
| |
| try { |
| XMLReader xr = XMLReaderFactory.createXMLReader() ; |
| xr.setFeature("http://xml.org/sax/features/namespace-prefixes", true) ; |
| // ResultSetXMLHandler1 handler = new ResultSetXMLHandler1() ; |
| ResultSetXMLHandler2 handler = new ResultSetXMLHandler2() ; |
| xr.setContentHandler(handler) ; |
| xr.parse(in) ; |
| if ( handler.isBooleanResult ) { |
| // Set superclass member |
| set(handler.askResult) ; |
| return ; |
| } |
| ResultSetStream rss = new ResultSetStream(handler.variables, model, |
| new QueryIterPlainWrapper(handler.results.iterator())) ; |
| // Set superclass member |
| set(rss) ; |
| } catch (SAXException ex) { |
| throw new ResultSetException("Problems parsing file (SAXException)", ex) ; |
| } catch (IOException ex) { |
| throw new ResultSetException("Problems parsing file (IOException)", ex) ; |
| } |
| |
| } |
| |
| static class ResultSetXMLHandler2 implements ContentHandler { |
| static final String namespace = XMLResults.dfNamespace ; |
| static final String variableElt = XMLResults.dfVariable ; |
| static final String resultElt = XMLResults.dfSolution ; |
| |
| // Boolean |
| boolean isBooleanResult = false ; |
| boolean askResult = false ; |
| |
| int rowCount = 0 ; |
| LabelToNode bNodes = SyntaxLabels.createLabelToNode(); |
| |
| boolean accumulate = false ; |
| StringBuffer buff = new StringBuffer() ; |
| List<String> variables = new ArrayList<>() ; |
| |
| List<Binding> results = new ArrayList<>() ; |
| // The current solution |
| BindingMap binding = null ; |
| |
| // Note on terminology: |
| // A "Binding" in ARQ is a set of name/value pairs |
| // In the XML format is it one pair. |
| |
| // Current value |
| String varName ; |
| String datatype = null ; |
| String langTag = null ; |
| |
| String rdfPrefix = "rdf" ; |
| |
| ResultSetXMLHandler2() {} |
| |
| @Override |
| public void setDocumentLocator(Locator locator) {} |
| |
| @Override |
| public void startDocument() throws SAXException {} |
| |
| @Override |
| public void endDocument() throws SAXException {} |
| |
| @Override |
| public void startPrefixMapping(String prefix, String uri) throws SAXException { |
| if ( uri.equals(RDF.getURI()) ) |
| rdfPrefix = prefix ; |
| } |
| |
| @Override |
| public void endPrefixMapping(String prefix) throws SAXException {} |
| |
| @Override |
| public void startElement(String ns, String localName, String qName, Attributes attrs) throws SAXException { |
| if ( !ns.equals(namespace) ) { |
| // Wrong namespace |
| return ; |
| } |
| |
| // ---- Header |
| |
| if ( localName.equals(XMLResults.dfVariable) ) { |
| if ( attrs.getValue(XMLResults.dfAttrVarName) != null ) { |
| String name = attrs.getValue(XMLResults.dfAttrVarName) ; |
| variables.add(name) ; |
| } |
| return ; |
| } |
| |
| // ---- Results |
| |
| if ( localName.equals(XMLResults.dfResults) ) |
| return ; |
| |
| // Boolean |
| if ( localName.equals(XMLResults.dfBoolean) ) { |
| isBooleanResult = true ; |
| // Wait for the content |
| } |
| |
| // ---- One solution |
| |
| if ( localName.equals(XMLResults.dfSolution) ) { |
| binding = BindingFactory.create() ; |
| return ; |
| } |
| |
| // One variable |
| |
| if ( localName.equals(XMLResults.dfBinding) ) { |
| varName = attrs.getValue(XMLResults.dfAttrVarName) ; |
| return ; |
| } |
| |
| // One value |
| |
| if ( localName.equals(XMLResults.dfURI) ) { |
| startElementURI(ns, localName, qName, attrs) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfLiteral) ) { |
| startElementLiteral(ns, localName, qName, attrs) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfBNode) ) { |
| startElementBNode(ns, localName, qName, attrs) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfUnbound) ) |
| return ; |
| |
| } |
| |
| @Override |
| public void endElement(String ns, String localName, String qName) throws SAXException { |
| if ( !ns.equals(namespace) ) { |
| // Wrong namespace |
| return ; |
| } |
| |
| // ---- Results |
| |
| if ( localName.equals(XMLResults.dfResults) ) |
| return ; |
| |
| if ( localName.equals(XMLResults.dfBoolean) ) { |
| endElementBoolean() ; |
| return ; |
| } |
| |
| // ---- One solution |
| |
| if ( localName.equals(XMLResults.dfSolution) ) { |
| varName = null ; |
| datatype = null ; |
| langTag = null ; |
| results.add(binding) ; |
| binding = null ; |
| return ; |
| } |
| |
| // ---- One variable |
| |
| if ( localName.equals(XMLResults.dfBinding) ) { |
| varName = null ; |
| return ; |
| } |
| // ---- One value. |
| |
| if ( localName.equals(XMLResults.dfURI) ) { |
| endElementURI(ns, localName, qName) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfLiteral) ) { |
| endElementLiteral(ns, localName, qName) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfBNode) ) { |
| endElementBNode(ns, localName, qName) ; |
| return ; |
| } |
| |
| if ( localName.equals(XMLResults.dfUnbound) ) |
| return ; |
| |
| } |
| |
| private boolean checkVarName(String cxtMsg) { |
| if ( cxtMsg == null ) |
| cxtMsg = "" ; |
| |
| if ( varName == null ) { |
| Log.warn(this, "No variable name in scope: " + cxtMsg) ; |
| return false ; |
| } |
| if ( !variables.contains(varName) ) { |
| Log.warn(this, "Variable name '" + varName + "'not declared: " + cxtMsg) ; |
| return false ; |
| } |
| return true ; |
| |
| } |
| |
| private void startElementURI(String ns, String localName, String name, Attributes attrs) { |
| startAccumulate() ; |
| } |
| |
| private void endElementURI(String ns, String localName, String name) { |
| endAccumulate() ; |
| String uri = buff.toString() ; |
| Node n = NodeFactory.createURI(uri) ; |
| if ( checkVarName("URI: " + uri) ) |
| addBinding(binding, Var.alloc(varName), n) ; |
| } |
| |
| private void startElementLiteral(String ns, String localName, String name, Attributes attrs) { |
| if ( attrs.getValue("datatype") != null ) |
| datatype = attrs.getValue("datatype") ; |
| |
| if ( attrs.getValue("xml:lang") != null ) |
| langTag = attrs.getValue("xml:lang") ; |
| |
| startAccumulate() ; |
| } |
| |
| private void endElementLiteral(String ns, String localName, String name) { |
| endAccumulate() ; |
| String lexicalForm = buff.toString() ; |
| |
| RDFDatatype dType = null ; |
| if ( datatype != null ) |
| dType = TypeMapper.getInstance().getSafeTypeByName(datatype) ; |
| |
| Node n = NodeFactory.createLiteral(lexicalForm.toString(), langTag, dType) ; |
| if ( checkVarName("Literal: " + FmtUtils.stringForNode(n)) ) |
| addBinding(binding, Var.alloc(varName), n) ; |
| |
| // Finished value - clear intermediates (the wonders of event based |
| // processing) |
| this.datatype = null ; |
| this.langTag = null ; |
| this.varName = null ; |
| return ; |
| } |
| |
| private void endElementBoolean() { |
| endAccumulate() ; |
| String result = buff.toString() ; |
| if ( result.equals("true") ) { |
| this.askResult = true ; |
| return ; |
| } |
| if ( result.equalsIgnoreCase("false") ) { |
| askResult = false ; |
| return ; |
| } |
| throw new ResultSetException("Unknown boolean value: " + result) ; |
| } |
| |
| private void startElementBNode(String ns, String localName, String name, Attributes attrs) { |
| startAccumulate() ; |
| } |
| |
| private void endElementBNode(String ns, String localName, String name) { |
| endAccumulate() ; |
| String bnodeId = buff.toString() ; |
| Node node = bNodes.get(null, bnodeId) ; |
| if ( checkVarName("BNode: " + bnodeId) ) |
| addBinding(binding, Var.alloc(varName), node) ; |
| } |
| |
| private void startAccumulate() { |
| buff.setLength(0) ; |
| accumulate = true ; |
| } |
| |
| private void endAccumulate() { |
| accumulate = false ; |
| } |
| |
| @Override |
| public void characters(char[] chars, int start, int finish) throws SAXException { |
| if ( accumulate ) { |
| if ( buff == null ) |
| buff = new StringBuffer() ; |
| buff.append(chars, start, finish) ; |
| } |
| } |
| |
| @Override |
| public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {} |
| |
| @Override |
| public void processingInstruction(String target, String data) throws SAXException {} |
| |
| @Override |
| public void skippedEntity(String name) throws SAXException {} |
| |
| static protected void addBinding(BindingMap binding, Var var, Node value) { |
| Node n = binding.get(var); |
| if ( n != null ) { |
| // Same - silently skip. |
| if ( n.equals(value) ) |
| return; |
| Log.warn(SPARQLResult.class, |
| String.format("Multiple occurences of a binding for variable '%s' with different values - ignored", var.getName())); |
| return; |
| } |
| binding.add(var, value); |
| } |
| |
| |
| } |
| } |