sparqlxml-parse
Version:
Parses SPARQL XML query results
229 lines • 10.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SparqlXmlParser = void 0;
const rdf_data_factory_1 = require("rdf-data-factory");
const saxes_1 = require("@rubensworks/saxes");
const readable_stream_1 = require("readable-stream");
/**
* Parser for the SPARQL Query Results XML format.
* @see https://www.w3.org/TR/rdf-sparql-XMLres/
*/
class SparqlXmlParser {
constructor(settings) {
settings = settings || {};
this.dataFactory = settings.dataFactory || new rdf_data_factory_1.DataFactory();
this.prefixVariableQuestionMark = !!settings.prefixVariableQuestionMark;
this.parseUnsupportedVersions = !!settings.parseUnsupportedVersions;
}
/**
* If the given version is valid for this parser to handle.
* @param version A version string.
*/
isValidVersion(version) {
return this.parseUnsupportedVersions || SparqlXmlParser.SUPPORTED_VERSIONS.includes(version);
}
/**
* Convert a SPARQL XML bindings response stream to a stream of bindings objects.
*
* The bindings stream will emit a 'variables' event that will contain
* the array of variables (as RDF.Variable[]), as defined in the response head.
*
* @param {NodeJS.ReadableStream} sparqlResponseStream A SPARQL XML response stream.
* @param version The version that was supplied as a media type parameter
* @return {NodeJS.ReadableStream} A stream of bindings.
*/
parseXmlResultsStream(sparqlResponseStream, version) {
const errorListener = (error) => resultStream.emit('error', error);
sparqlResponseStream.on('error', errorListener);
const parser = new saxes_1.SaxesParser();
const stack = [];
let variablesFound = false;
let resultsFound = false;
const variables = [];
let currentBindings = {};
let currentBindingName = '';
let currentBindingType = '';
let currentBindingAnnotation;
let currentText = '';
let currentQuotedTriples = [];
parser.on("error", errorListener);
parser.on("opentag", tag => {
if (tag.name === "variable" && this.stackEquals(stack, ['sparql', 'head'])) {
variables.push(this.dataFactory.variable(tag.attributes.name));
}
else if (tag.name === "results" && this.stackEquals(stack, ['sparql'])) {
resultsFound = true;
}
else if (tag.name === 'result' && this.stackEquals(stack, ['sparql', 'results'])) {
currentBindings = {};
}
else if (tag.name === 'binding' && this.stackEquals(stack, ['sparql', 'results', 'result'])) {
currentBindingName = tag.attributes.name || '';
currentBindingType = '';
currentBindingAnnotation = undefined;
currentText = '';
currentQuotedTriples = [];
}
else if (tag.name === 'triple' && this.stackBeginsWith(stack, ['sparql', 'results', 'result'])) {
currentQuotedTriples.push({ components: {} });
}
else if (stack[stack.length - 1] === 'triple' && this.stackBeginsWith(stack, ['sparql', 'results', 'result', 'binding'])) {
currentBindingType = '';
currentBindingAnnotation = undefined;
currentText = '';
if (!['subject', 'predicate', 'object'].includes(tag.name)) {
errorListener(new Error(`Illegal quoted triple component '${tag.name}' found on line ${parser.line + 1}`));
}
else {
currentQuotedTriples[currentQuotedTriples.length - 1].currentComponent = tag.name;
}
}
else if (this.stackBeginsWith(stack, ['sparql', 'results', 'result', 'binding'])) {
currentBindingType = tag.name;
if ('xml:lang' in tag.attributes) {
currentBindingAnnotation = {
language: tag.attributes['xml:lang'],
direction: tag.attributes['its:dir'],
};
}
else if ('datatype' in tag.attributes) {
currentBindingAnnotation = this.dataFactory.namedNode(tag.attributes.datatype);
}
else {
currentBindingAnnotation = undefined;
}
}
else if (tag.name === 'sparql' && tag.attributes.version) {
if (!this.isValidVersion(tag.attributes.version)) {
resultStream.emit("error", new Error(`Detected unsupported version: ${tag.attributes.version}`));
}
resultStream.emit('version', tag.attributes.version);
}
stack.push(tag.name);
});
parser.on("closetag", tag => {
if (this.stackEquals(stack, ['sparql', 'head'])) {
resultStream.emit("variables", variables);
variablesFound = true;
}
if (this.stackEquals(stack, ['sparql', 'results', 'result'])) {
resultStream.push(currentBindings);
}
if (this.stackBeginsWith(stack, ['sparql', 'results', 'result', 'binding'])) {
// Determine current RDF term value
let term;
if (!currentBindingName && currentBindingType) {
errorListener(new Error(`Terms should have a name on line ${parser.line + 1}`));
}
else if (currentBindingType === 'uri') {
term = this.dataFactory.namedNode(currentText);
}
else if (currentBindingType === 'bnode') {
term = this.dataFactory.blankNode(currentText);
}
else if (currentBindingType === 'literal') {
term = this.dataFactory.literal(currentText, currentBindingAnnotation);
}
else if (stack[stack.length - 1] === 'triple') {
const currentQuotedTriple = currentQuotedTriples.pop();
if (currentQuotedTriple && currentQuotedTriple.components.subject && currentQuotedTriple.components.predicate && currentQuotedTriple.components.object) {
term = this.dataFactory.quad(currentQuotedTriple.components.subject, currentQuotedTriple.components.predicate, currentQuotedTriple.components.object);
}
else {
errorListener(new Error(`Incomplete quoted triple on line ${parser.line + 1}`));
}
}
else if (currentBindingType) {
errorListener(new Error(`Invalid term type '${currentBindingType}' on line ${parser.line + 1}`));
}
if (term) {
if (currentQuotedTriples.length > 0) {
// If we're in a quoted triple, store the term inside the active quoted triple
const currentQuotedTriple = currentQuotedTriples[currentQuotedTriples.length - 1];
if (currentQuotedTriple.components[currentQuotedTriple.currentComponent]) {
errorListener(new Error(`The ${currentQuotedTriple.currentComponent} in a quoted triple on line ${parser.line + 1} was already defined before`));
}
currentQuotedTriple.components[currentQuotedTriple.currentComponent] = term;
}
else {
// Store the value in the current bindings object
const key = this.prefixVariableQuestionMark ? ('?' + currentBindingName) : currentBindingName;
currentBindings[key] = term;
}
}
currentBindingType = undefined;
}
stack.pop();
});
parser.on("text", text => {
if (this.stackBeginsWith(stack, ['sparql', 'results', 'result', 'binding']) && stack[stack.length - 1] === currentBindingType) {
currentText = text;
}
});
const resultStream = sparqlResponseStream
.on("end", _ => {
if (!resultsFound) {
resultStream.emit("error", new Error("No valid SPARQL query results were found."));
}
else if (!variablesFound) {
resultStream.emit('variables', []);
}
})
.pipe(new readable_stream_1.Transform({
objectMode: true,
transform(chunk, encoding, callback) {
parser.write(chunk);
callback();
}
}));
if (version && !this.isValidVersion(version)) {
resultStream.destroy(new Error(`Detected unsupported version as media type parameter: ${version}`));
}
return resultStream;
}
/**
* Convert a SPARQL XML boolean response stream to a promise resolving to a boolean.
* This will reject if the given response was not a valid boolean response.
* @param {NodeJS.ReadableStream} sparqlResponseStream A SPARQL XML response stream.
* @param version The version that was supplied as a media type parameter
* @return {Promise<boolean>} The response boolean.
*/
parseXmlBooleanStream(sparqlResponseStream, version) {
if (version && !this.isValidVersion(version)) {
return Promise.reject(new Error(`Detected unsupported version as media type parameter: ${version}`));
}
return new Promise((resolve, reject) => {
const parser = new saxes_1.SaxesParser();
const stack = [];
parser.on("error", reject);
parser.on("opentag", tag => {
stack.push(tag.name);
});
parser.on("closetag", _ => {
stack.pop();
});
parser.on("text", text => {
if (this.stackEquals(stack, ['sparql', 'boolean'])) {
resolve(text === 'true');
}
});
sparqlResponseStream
.on('error', reject)
.on('data', d => parser.write(d))
.on('end', () => reject(new Error('No valid ASK response was found.')));
});
}
stackEquals(a, b) {
return a.length === b.length && a.every((v, i) => b[i] === v);
}
stackBeginsWith(a, b) {
return a.length >= b.length && b.every((v, i) => a[i] === v);
}
}
exports.SparqlXmlParser = SparqlXmlParser;
SparqlXmlParser.SUPPORTED_VERSIONS = [
'1.2',
'1.2-basic',
'1.1',
];
//# sourceMappingURL=SparqlXmlParser.js.map