/*
* 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.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.sparql.util.LabelToNodeMap ;
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. */
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 ;
LabelToNodeMap bNodes = LabelToNodeMap.createBNodeMap() ;
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.asNode(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 {}
}
}