Skip to content

Commit

Permalink
Add SparqlXmlParser
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed Aug 21, 2018
1 parent 3fa0980 commit b15c010
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 3 deletions.
2 changes: 2 additions & 0 deletions index.ts
@@ -0,0 +1,2 @@
export * from "./lib/SparqlXmlParser";
export * from "./lib/SparqlXmlBindingsTransformer";
20 changes: 20 additions & 0 deletions lib/SparqlXmlBindingsTransformer.ts
@@ -0,0 +1,20 @@
import {Transform, TransformCallback} from "stream";
import {SparqlXmlParser} from "./SparqlXmlParser";

/**
* Transforms a stream of SPARQL JSON bindings object to parsed bindings.
*/
export class SparqlXmlBindingsTransformer extends Transform {

private readonly parser: SparqlXmlParser;

constructor(parser: SparqlXmlParser) {
super({ objectMode: true });
this.parser = parser;
}

public _transform(chunk: any, encoding: string, callback: TransformCallback): void {
callback(null, this.parser.parseXmlBindings(chunk));
}

}
119 changes: 119 additions & 0 deletions lib/SparqlXmlParser.ts
@@ -0,0 +1,119 @@
import * as DefaultDataFactory from "@rdfjs/data-model";
import * as RDF from "rdf-js";
import {Readable} from "stream";
import {SparqlXmlBindingsTransformer} from "./SparqlXmlBindingsTransformer";
// tslint:disable-next-line:no-var-requires
const XmlStream = require('xml-stream');

/**
* Parser for the SPARQL Query Results XML format.
* @see https://www.w3.org/TR/rdf-sparql-XMLres/
*/
export class SparqlXmlParser {

private readonly dataFactory: RDF.DataFactory;
private readonly prefixVariableQuestionMark?: boolean;

constructor(settings?: ISettings) {
settings = settings || {};
this.dataFactory = settings.dataFactory || DefaultDataFactory;
this.prefixVariableQuestionMark = !!settings.prefixVariableQuestionMark;
}

/**
* 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.
* @return {NodeJS.ReadableStream} A stream of bindings.
*/
public parseXmlResultsStream(sparqlResponseStream: NodeJS.ReadableStream): NodeJS.ReadableStream {
const variables: RDF.Variable[] = [];

const rawResultStream = new Readable({ objectMode: true });
rawResultStream._read = () => { return; };

const xmlParser = new XmlStream(sparqlResponseStream);
xmlParser.collect('binding', true);
xmlParser.on('error', (error: Error) => resultStream.emit('error', error));
xmlParser.on('endElement: head > variable', (node: any) => variables.push(this.dataFactory.variable(node.$.name)));
xmlParser.on('endElement: results result', (bindings: any) => rawResultStream.push(bindings));
xmlParser.on('end', () => {
resultStream.emit('variables', variables);
rawResultStream.push(null);
});

const resultStream = rawResultStream.pipe(new SparqlXmlBindingsTransformer(this));
sparqlResponseStream.on('error', (error) => resultStream.emit('error', error));
return resultStream;
}

/**
* Convert a SPARQL XML result binding to a bindings object.
* @param rawBindings A SPARQL XML result binding.
* @return {IBindings} A bindings object.
*/
public parseXmlBindings(rawBindings: any): IBindings {
const bindings: IBindings = {};
for (const binding of rawBindings.binding) {
const key = binding.$.name;
let value: RDF.Term = null;
if (binding.bnode) {
value = this.dataFactory.blankNode(binding.bnode);
} else if (binding.literal) {
if (binding.literal.$ && binding.literal.$['xml:lang']) {
value = this.dataFactory.literal(binding.literal.$text, binding.literal.$['xml:lang']);
} else if (binding.literal.$ && binding.literal.$.datatype) {
value = this.dataFactory.literal(binding.literal.$text,
this.dataFactory.namedNode(binding.literal.$.datatype));
} else {
value = this.dataFactory.literal(binding.literal);
}
} else {
value = this.dataFactory.namedNode(binding.uri);
}
bindings[this.prefixVariableQuestionMark ? ('?' + key) : key] = value;
}
return bindings;
}

/**
* Convert a SPARQL XML boolean response stream to a promise resolving to a boolean.
* This will reject if the given reponse was not a valid boolean response.
* @param {NodeJS.ReadableStream} sparqlResponseStream A SPARQL XML response stream.
* @return {NodeJS.ReadableStream} A stream of bindings.
*/
public parseXmlBooleanStream(sparqlResponseStream: NodeJS.ReadableStream): Promise<boolean> {
return new Promise((resolve, reject) => {
sparqlResponseStream.on('error', reject);
const xmlParser = new XmlStream(sparqlResponseStream);
xmlParser.on('error', reject);
xmlParser.on('endElement: boolean', (node: any) => resolve(node.$text === 'true'));
xmlParser.on('end', () => reject(new Error('No valid ASK response was found.')));
});
}

}

/**
* Constructor settings object interface for {@link SparqlXmlParser}.
*/
export interface ISettings {
/**
* A custom datafactory.
*/
dataFactory?: RDF.DataFactory;
/**
* If variable names should be prefixed with a quotation mark.
*/
prefixVariableQuestionMark?: boolean;
}

/**
* A bindings object.
*/
export interface IBindings {
[key: string]: RDF.Term;
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -70,6 +70,7 @@
"prepare": "npm run build"
},
"dependencies": {
"@rdfjs/data-model": "^1.1.0"
"@rdfjs/data-model": "^1.1.0",
"xml-stream": "^0.4.5"
}
}
42 changes: 40 additions & 2 deletions yarn.lock
Expand Up @@ -382,6 +382,10 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"

bindings@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"

brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
Expand Down Expand Up @@ -1210,6 +1214,12 @@ iconv-lite@0.4.23, iconv-lite@^0.4.4:
dependencies:
safer-buffer ">= 2.1.2 < 3"

iconv@^2.1.4:
version "2.3.0"
resolved "https://registry.yarnpkg.com/iconv/-/iconv-2.3.0.tgz#9739887c2bd492d9a5e236dd3667c5358601201b"
dependencies:
nan "^2.3.5"

ignore-walk@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
Expand All @@ -1234,7 +1244,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"

inherits@2, inherits@^2.0.3, inherits@~2.0.3:
inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"

Expand Down Expand Up @@ -2130,7 +2140,7 @@ ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"

nan@^2.9.2:
nan@^2.10.0, nan@^2.3.5, nan@^2.9.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"

Expand Down Expand Up @@ -2162,6 +2172,13 @@ needle@^2.2.1:
iconv-lite "^0.4.4"
sax "^1.2.4"

node-expat@^2.3.1:
version "2.3.17"
resolved "https://registry.yarnpkg.com/node-expat/-/node-expat-2.3.17.tgz#5fab92c16737ec5b9beafdeba99ecdaba1ebc466"
dependencies:
bindings "^1.2.1"
nan "^2.10.0"

node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
Expand Down Expand Up @@ -2544,6 +2561,15 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"

readable-stream@^1.0.31:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"

readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.2.2:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
Expand Down Expand Up @@ -2936,6 +2962,10 @@ string-width@^1.0.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"

string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"

string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
Expand Down Expand Up @@ -3330,6 +3360,14 @@ xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"

xml-stream@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/xml-stream/-/xml-stream-0.4.5.tgz#7452d85b37f9b881a70eff0cf74a0df02088edeb"
dependencies:
iconv "^2.1.4"
node-expat "^2.3.1"
readable-stream "^1.0.31"

y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
Expand Down

0 comments on commit b15c010

Please sign in to comment.