User:Mvolz (WMF)/CiteToolWIP.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**
Enable the script with the following code in your common.js
mw.loader.using(['wikibase'], function() {
$.getScript( 'https://www.wikidata.org/w/index.php?title=User:Mvolz_(WMF)/CiteTool.js&action=raw&ctype=text/javascript', function() {
var citeTool = new wb.CiteTool( 'https://www.wikidata.org/w/index.php?title=User:Mvolz_(WMF)/CiteProperties.json&action=raw&ctype=application/json' );
citeTool.init();
});
});
*/
( function( wb, dv, mw, $ ) {
'use strict';
function CiteTool( configUrl ) {
this.configUrl = configUrl;
this.config = null;
this.citoidClient = new mw.CitoidClient();
this.citeToolReferenceEditor = null;
this.citeToolAutofillLinkRenderer = null;
}
CiteTool.prototype.init = function() {
var self = this;
if ( !mw.config.exists( 'wbEntityId' ) ) {
return;
}
$( '.wikibase-entityview' )
.on( 'referenceviewafterstartediting', function( e ) {
self.initAutofillLink( e.target );
} );
// @fixme the event also fires for other changes, like editing qualifiers
$( '.wikibase-entityview' )
.on( 'snakviewchange', function( e ) {
self.initAutofillLink( e.target );
} );
this.pendingDialog = new mw.PendingDialog( {
size: 'small'
} );
this.windowManager = new OO.ui.WindowManager();
$( 'body' ).append( this.windowManager.$element );
this.windowManager.addWindows( [ this.pendingDialog ] );
};
CiteTool.prototype.getConfig = function() {
var dfd = $.Deferred();
$.ajax({
url: this.configUrl,
dataType: 'json',
success: function( config ) {
dfd.resolve( config );
},
error: function( result ) {
console.log( 'Error loading citoid config' );
}
});
return dfd.promise();
};
CiteTool.prototype.initAutofillLink = function( target ) {
var self = this;
if ( this.config === null ) {
this.getConfig()
.done( function( config ) {
self.config = config;
self.citeToolReferenceEditor = new wb.CiteToolReferenceEditor( config, self.windowManager, self.pendingDialog);
self.citeToolAutofillLinkRenderer = new wb.CiteToolAutofillLinkRenderer(
config,
self.citoidClient,
self.citeToolReferenceEditor,
self.windowManager,
self.pendingDialog
);
self.checkReferenceAndAddAutofillLink( target );
} );
} else {
var refViews = $( target ).closest( '.wikibase-referenceview' );
self.checkReferenceAndAddAutofillLink( refViews[0] );
}
};
CiteTool.prototype.checkReferenceAndAddAutofillLink = function( target ) {
if ( $( target ).find( '.wikibase-citetool-autofill' ).length > 0 ) {
return;
}
var reference = this.getReferenceFromView( target );
if ( !reference ) {
return;
}
this.citeToolAutofillLinkRenderer.renderLink( target );
if ( this.getLookupSnakProperty( reference ) !== null ) {
// Disable for invalid properties
this.citeToolAutofillLinkRenderer.disableLink( target );
} else {
// Enable if property is valid
this.citeToolAutofillLinkRenderer.enableLink( target );
}
};
CiteTool.prototype.getReferenceFromView = function( referenceView ) {
// not a reference view change
if ( referenceView === undefined ) {
return null;
}
var refView = $( referenceView ).data( 'referenceview' );
return refView.value();
};
CiteTool.prototype.getLookupSnakProperty = function( reference ) {
var snaks = reference.getSnaks(),
lookupProperties = this.getLookupProperties(),
lookupProperty = null;
snaks.each( function( k, snak ) {
var propertyId = snak.getPropertyId();
if ( lookupProperties.indexOf( propertyId ) !== -1 ) {
if ( lookupProperty === null ) {
lookupProperty = propertyId;
}
}
} );
return lookupProperty;
};
CiteTool.prototype.getLookupProperties = function() {
var properties = [];
if ( this.config.properties ) {
properties = Object.keys( this.config.properties );
}
return properties;
};
wb.CiteTool = CiteTool;
}( wikibase, dataValues, mediaWiki, jQuery ) );
( function( wb, dv, mw, $ ) {
'use strict';
function CiteToolAutofillLinkRenderer( config, citoidClient, citeToolReferenceEditor, windowManager, pendingDialog ) {
this.config = config;
this.citoidClient = citoidClient;
this.citeToolReferenceEditor = citeToolReferenceEditor;
this.windowManager = windowManager;
this.pendingDialog = pendingDialog;
}
CiteToolAutofillLinkRenderer.prototype.renderLink = function( referenceView ) {
var self = this;
var options = {
templateParams: [
'wikibase-citetool-autofill wikibase-referenceview-autofill', // CSS class names
'#', // URL
'autofill', // Label
'' // Title tooltip
],
cssClassSuffix: 'autofill'
};
var $span = $( '<span/>' )
.toolbarbutton( options )
.on( 'click', function( e ) {
e.preventDefault();
self.onAutofillClick( e.target );
} );
this.getReferenceToolbarContainer( referenceView ).append( $span );
};
CiteToolAutofillLinkRenderer.prototype.disableLink = function( target ){
var buttons = $( target ).find( '.wikibase-citetool-autofill' );
var button;
if ( buttons.length > 0 ){
button = buttons[0];
button.toolbarbutton.disable();
}
};
CiteToolAutofillLinkRenderer.prototype.enableLink = function( target ){
var buttons = $( target ).find( '.wikibase-citetool-autofill' );
var button;
if ( buttons.length > 0 ){
button = buttons[0];
button.toolbarbutton.enable();
}
};
CiteToolAutofillLinkRenderer.prototype.getReferenceFromView = function( referenceView ) {
// not a reference view change
if ( referenceView === undefined ) {
return null;
}
var refView = $( referenceView ).data( 'referenceview' );
return refView.value();
};
CiteToolAutofillLinkRenderer.prototype.getLookupSnakProperty = function( reference ) {
var snaks = reference.getSnaks(),
lookupProperties = this.getLookupProperties(),
lookupProperty = null;
snaks.each( function( k, snak ) {
var propertyId = snak.getPropertyId();
if ( lookupProperties.indexOf( propertyId ) !== -1 ) {
if ( lookupProperty === null ) {
lookupProperty = propertyId;
}
}
} );
return lookupProperty;
};
CiteToolAutofillLinkRenderer.prototype.getLookupProperties = function() {
var properties = [];
if ( this.config.properties ) {
properties = Object.keys( this.config.properties );
}
return properties;
};
CiteToolAutofillLinkRenderer.prototype.getReferenceToolbarContainer = function( referenceView ) {
var $heading = $( referenceView ).find( '.wikibase-referenceview-heading' ),
$toolbar = $heading.find( '.wikibase-toolbar-container' );
return $toolbar;
};
CiteToolAutofillLinkRenderer.prototype.onAutofillClick = function( target ) {
var referenceView = $( target ).closest( '.wikibase-referenceview' ),
reference = this.getReferenceFromView( referenceView ),
self = this;
if ( reference === null ) {
return;
}
var value = this.getLookupSnakValue( reference );
self.windowManager.openWindow( self.pendingDialog );
self.pendingDialog.pushPending();
self.pendingDialog.getActionProcess.call( self.pendingDialog, 'waiting' );
this.citoidClient.search( value )
.then(
//success
function( data ) {
if ( data[0] ) {
self.citeToolReferenceEditor.addReferenceSnaksFromCitoidData(
data[0],
referenceView
);
}
},
//failure
function() {
self.pendingDialog.popPending();
self.pendingDialog.getActionProcess.call( self.pendingDialog, 'error' );
}
);
};
CiteToolAutofillLinkRenderer.prototype.getLookupSnakValue = function( reference ) {
var value = null,
lookupProperties = this.getLookupProperties();
reference.getSnaks().each( function( k, snak ) {
var propertyId = snak.getPropertyId();
if ( lookupProperties.indexOf( propertyId ) !== -1 ) {
value = snak.getValue().getValue();
}
} );
return value;
};
wb.CiteToolAutofillLinkRenderer = CiteToolAutofillLinkRenderer;
}( wikibase, dataValues, mediaWiki, jQuery ) );
( function( wb, dv, mw, $ ) {
'use strict';
function CiteToolReferenceEditor( config, windowManager, pendingDialog ) {
this.config = config;
this.windowManager = windowManager;
this.pendingDialog = pendingDialog;
this.citoidClient = new mw.CitoidClient();
this.openRefineClient = new mw.OpenRefineClient();
this.sparql = new wb.queryService.api.Sparql();
this.lc = new mw.LanguageConverter();
}
CiteToolReferenceEditor.prototype.addReferenceSnaksFromCitoidData = function( data, referenceView ) {
var publicationTypes = [
'Q5633421',
'Q41298',
'Q1110794',
'Q35127',
'Q32635',
"Q838948",
"Q5057302",
"Q686822",
"Q17928402",
"Q571",
"Q3331189",
"Q1980247",
"Q2334719",
"Q5057302",
"Q23927052",
"Q4423781",
"Q49848",
"Q9158",
"Q17329259",
"Q11424",
"Q7216866",
"Q545861",
"Q30070565",
"Q178651",
"Q191067",
"Q133492",
"Q191067",
"Q87167",
"Q4006",
"Q191067",
"Q253623",
"Q5057302",
"Q604733",
"Q1555508",
"Q10870555",
"Q820655",
"Q1266946",
"Q21191270",
"Q30070675",
"Q36774"
];
var statedIn = "P248";
var refView = $( referenceView ).data( 'referenceview' ),
lv = this.getReferenceSnakListView( refView ),
// usedProperties = refView.value().getSnaks().getPropertyOrder(),
self = this;
var addedSnakItem = false;
var snakPromises = [];
// Try to add "stated in" statement by search via doi
var doiProm = self.openRefineClient.search( data.DOI ).then( function ( results ) {
console.log('doi');
if ( results.result[0] ) {
lv.addItem(
self.getWikibaseItemSnak( statedIn, results.result[0].id )
);
addedSnakItem = true;
}
});
snakPromises.push(doiProm);
$.each( data, function( key, val ) {
var propertyId = self.getPropertyForCitoidData( key );
// Some default values for an openRefine query
var query = {
query : val,
limit : 1,
type : null,
type_strict : "any"
};
// Method used for adding item snaks with OpenRefine
function addItemSnak( q ) {
return self.openRefineClient.search( q ).then( function ( results ) {
console.log( propertyId );
if ( results.result[0] ) {
lv.addItem(
self.getWikibaseItemSnak( propertyId, results.result[0].id )
);
addedSnakItem = true;
}
});
}
// var properties = [
// { p : self.getPropertyForCitoidData("DOI"), v : data.DOI }
// ];
// var properties = [
// { p : self.getPropertyForCitoidData("ISSN"), v : data.ISSN }
// ];
if ( !propertyId ) {
console.log( "PropertyID missing for key: " + key );
return;
}
// Allow duplicate properties; i.e. multiple authors
// TODO: Exclude identical snaks with same property and value
// if ( propertyId !== null && usedProperties.indexOf( propertyId ) !== -1 ) {
// return;
// }
switch ( key ) {
// Monolingual properties
case 'title':
lv.addItem( self.getMonolingualValueSnak(
propertyId,
val,
self.getTitleLanguage( data )
) );
addedSnakItem = true;
break;
// Date properties
case 'date':
case 'accessDate':
try {
lv.addItem(
self.getDateSnak( propertyId, val )
);
addedSnakItem = true;
} catch ( e ) {
console.log( e );
}
break;
// Item properties
case 'language':
query.type = ['Q1288568', 'Q33742', 'Q951873', 'Q33384', 'Q34770', 'Q1002697']; // Modern language, natural language, language, ethnolect, dialect
snakPromises.push( addItemSnak( query ) );
break;
case 'publicationTitle':
//query.type = null; // Too many to enumerate fully, do by score instead?
query.type = publicationTypes;
snakPromises.push( addItemSnak( query ) );
lv.addItem( self.getMonolingualValueSnak(
'P6333',
val,
self.getTitleLanguage( data )
) );
addedSnakItem = true;
break;
case 'publisher':
query.type = ['Q2085381', 'Q479716', 'Q1114515', 'Q149985', 'Q748822', 'Q18127', 'Q2024496', 'Q327333'];
snakPromises.push( addItemSnak( query ) );
break;
// number properties
case 'numPages':
break;
// String properties
case 'author':
case 'contributor':
case 'editor':
case 'translator':
case 'seriesEditor':
case 'interviewee':
case 'interviewer':
case 'director':
case 'scriptwriter':
case 'producer':
case 'castMember':
case 'sponsor':
case 'counsel':
case 'inventor':
case 'attorneyAgent':
case 'recipient':
case 'performer':
case 'composer':
case 'wordsBy':
case 'cartographer':
case 'programmer':
case 'artist':
case 'commenter':
case 'presenter':
case 'guest':
case 'podcaster':
case 'reviewedAuthor':
case 'cosponsor':
case 'bookAuthor':
for (var i = 0; i < val.length; i++) {
try {
lv.addItem(
self.getStringSnak( propertyId, val[i][0] + " " + val[i][1] )
);
addedSnakItem = true;
}
catch( e ) {
console.log(e);
}
}
break;
case 'ISSN':
case 'ISBN':
case 'PMID':
case 'url':
case 'pages':
case 'issue':
case 'volume':
case 'PMCID':
case 'DOI':
var str = false;
if (typeof val === 'string') {
str = true;
try {
lv.addItem(
self.getStringSnak( propertyId, val )
);
addedSnakItem = true;
}
catch( e ) {
console.log(e);
}
// For array of identifiers, add every one
} else if ( Array.isArray( val ) ) {
for (i = 0; i < val.length; i++) {
try {
lv.addItem(
self.getStringSnak( propertyId, val[i] )
);
addedSnakItem = true;
}
catch( e ) {
console.log(e);
}
}
}
// Below currently does not work
//var queryProperty = self.getQueryPropertyForCitoidData( key );
// Very hacky - should try every id in Array instead of just first one
//if (!str) {
// val = val[0];
//}
// self.lookupItemByIdentifier( queryProperty, val )
// .done( function( itemId ) {
// console.log( itemId );
// console.log( propertyId );
// try {
// if (itemId && propertyId) {
// lv.addItem(
// self.getWikibaseItemSnak( propertyId, itemId )
// );
// addedSnakItem = true;
// }
// } catch( e ) {}
// } );
break;
default:
break;
}
} );
if ( addedSnakItem === true ) {
$.when.apply( $, snakPromises ).then( function() {
lv.startEditing().then( function() {
self.pendingDialog.popPending();
self.windowManager.closeWindow( self.pendingDialog );
});
});
}
};
CiteToolReferenceEditor.prototype.getReferenceSnakListView = function( refView ) {
var refListView = refView.$listview.data( 'listview' ),
snakListView = refListView.items(),
snakListViewData = snakListView.data( 'snaklistview' ),
listView = snakListViewData.$listview.data( 'listview' );
return listView;
};
CiteToolReferenceEditor.prototype.getPropertyForCitoidData = function( key ) {
if ( this.config.zoteroProperties[key] ) {
return this.config.zoteroProperties[key];
}
return null;
};
CiteToolReferenceEditor.prototype.getQueryPropertyForCitoidData = function( key ) {
if ( this.config.queryProperties[key] ) {
return this.config.queryProperties[key];
}
return null;
};
CiteToolReferenceEditor.prototype.getTitleLanguage = function( data ) {
return this.lc.getMonolingualCode(data.language);
};
CiteToolReferenceEditor.prototype.getMonolingualValueSnak = function( propertyId, title, languageCode ) {
return new wb.datamodel.PropertyValueSnak(
propertyId,
new dv.MonolingualTextValue( languageCode, title )
);
};
CiteToolReferenceEditor.prototype.getStringSnak = function( propertyId, val) {
return new wb.datamodel.PropertyValueSnak(
propertyId,
new dv.StringValue( val )
);
};
CiteToolReferenceEditor.prototype.getNumSnak = function( propertyId, val) {
return new wb.datamodel.NumberValueSnak(
propertyId,
new dv.StringValue( val )
);
};
CiteToolReferenceEditor.prototype.getDateSnak = function( propertyId, dateString ) {
var timestamp = dateString + 'T00:00:00Z';
return new wb.datamodel.PropertyValueSnak(
propertyId,
new dv.TimeValue( timestamp )
);
};
CiteToolReferenceEditor.prototype.getWikibaseItemSnak = function( propertyId, itemId ) {
return new wb.datamodel.PropertyValueSnak(
propertyId,
new wb.datamodel.EntityId( itemId )
);
};
CiteToolReferenceEditor.prototype.lookupItemByIdentifier = function( propertyId, value ) {
var query = "SELECT ?identifier ?entity "
+ "WHERE { ?entity wdt:" + propertyId + " ?identifier . "
+ "FILTER ( ?identifier in ('" + value + "') ) "
+ "}";
var dfd = $.Deferred(),
baseUrl = 'https://query.wikidata.org/bigdata/namespace/wdq/sparql';
$.ajax( {
method: 'GET',
url: baseUrl,
data: {
query: query,
format: 'json'
}
} )
.done( function( data ) {
var uriPattern = /^http:\/\/www.wikidata.org\/entity\/([PQ]\d+)$/;
if ( data.results.bindings.length > 0 ) {
var result = data.results.bindings[0],
uri = result.entity.value,
matches = uri.match(uriPattern);
dfd.resolve( matches[1] );
} else {
dfd.resolve( false );
}
} );
return dfd.promise();
};
wb.CiteToolReferenceEditor = CiteToolReferenceEditor;
}( wikibase, dataValues, mediaWiki, jQuery ) );
( function( mw, $ ) {
'use strict';
function CitoidClient() {
}
CitoidClient.prototype.search = function( value ) {
var baseUrl = 'https://en.wikipedia.org/api/rest_v1/data/citation',
format = 'mediawiki-basefields',
url = baseUrl + '/' + format + '/' + encodeURIComponent(value);
return $.ajax( {
method: 'GET',
url: url
} );
};
mw.CitoidClient = CitoidClient;
}( mediaWiki, jQuery ) );
( function( mw, $ ) {
'use strict';
function LanguageConverter() {
this.monolingualCodes= null;
this.contentCodes = null;
this.ready = false;
}
LanguageConverter.prototype.init = function () {
this.service = new mw.Api();
const self = this;
// Cache multilingual codes
this.service
.get( {
action: "query",
format: "json",
meta: "wbcontentlanguages",
formatversion: "2",
wbclcontext: "monolingualtext"
} )
.then ( function ( results ) {
if ( results && results.query && results.query.wbcontentlanguages ) {
self.monolingualCodes = results.query.wbcontentlanguages;
}
} );
// Cache content language codes
this.service
.get( {
action: "query",
format: "json",
meta: "wbcontentlanguages",
formatversion: "2",
wbclcontext: "term"
} )
.then ( function ( results ) {
if ( results && results.query && results.query.wbcontentlanguages ) {
self.contentCodes = results.query.wbcontentlanguages;
}
} );
};
// Get IETF language code root as fallback
const getCodeFallback = function ( code ) {
code = code.split( "-" );
code = code[0];
return code;
};
// Common to getContentCode and getMonolingualCode
const getCode = function( value, cachedCodes, defaultCode ) {
if ( !value ) {
return defaultCode;
}
value = value.toLowerCase();
let code = this.cachedCodes[ value ];
if ( !code ) {
code = getCodeFallback( value );
code = this.cachedCodes[ code ];
if ( !code ) {
return defaultCode;
}
}
code = code.code;
return code;
};
// Get valid codes for multilingual text
LanguageConverter.prototype.getMonolingualCode = function( value ) {
return getCode( value, this.monolingualCodes, 'und');
};
// Get valid codes for content languages i.e. labels and desc
LanguageConverter.prototype.getContentCode = function( value ) {
return getCode( value, this.contentCodes, 'en');
};
LanguageConverter.prototype.getQID = function( value ) {
// fallback
return false;
};
mw.LanguageConverter = LanguageConverter;
}( mediaWiki, jQuery ) );
( function( mw, $ ) {
'use strict';
function OpenRefineClient() {
}
OpenRefineClient.prototype.search = function( query ) {
var url = 'https://tools.wmflabs.org/openrefine-wikidata/en/api';
if ( typeof query === 'string' ){
return $.ajax( {
method: 'GET',
url: url,
crossDomain: true,
data: {
query: query
}
} );
} else {
return $.ajax( {
method: 'GET',
url: url,
crossDomain: true,
data: {
query: JSON.stringify(query)
}
} );
}
};
mw.OpenRefineClient = OpenRefineClient;
}( mediaWiki, jQuery ) );
( function( mw, $ ) {
'use strict';
function PendingDialog( config ) {
PendingDialog.super.call( this, config );
}
OO.inheritClass( PendingDialog, OO.ui.ProcessDialog );
// Specify a name for .addWindows()
PendingDialog.static.name = 'PendingDialog';
// Specify a title statically (or, alternatively, with data passed to the opening() method).
PendingDialog.static.title = 'Generating Citation';
PendingDialog.static.actions = [
{ action: 'waiting', modes: 'edit', label: 'Searching' },
{ action: 'error', modes: 'edit', label: 'Error' }
];
// Customize the initialize() function: This is where to add content to the dialog body and set up event handlers.
PendingDialog.prototype.initialize = function () {
// Call the parent method
PendingDialog.super.prototype.initialize.apply( this, arguments );
// Create and append a layout and some content.
this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
this.panel1.$element.append( '<p>Generating a reference for you.... please wait...</p>' );
this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
this.panel2.$element.append( '<p>Unable to generate reference from this input. Press escape to exit.</p>' );
this.stackLayout= new OO.ui.StackLayout( {
items: [ this.panel1, this.panel2 ]
} );
this.stackLayout.setItem( this.panel1 );
this.$body.append( this.stackLayout.$element );
};
PendingDialog.prototype.getSetupProcess = function ( data ) {
return PendingDialog.super.prototype.getSetupProcess.call( this, data )
.next( function () {
this.actions.setMode( 'waiting' );
}, this );
};
// Specify processes to handle the actions.
PendingDialog.prototype.getActionProcess = function ( action ) {
if ( action === 'waiting' ) {
//this.pushPending();
// Set the mode to help.
this.actions.setMode( 'waiting' );
// Show the help panel.
this.stackLayout.setItem( this.panel1 );
}
if ( action === 'error' ) {
//this.popPending();
// Set the mode to help.
this.actions.setMode( 'error' );
// Show the help panel.
this.stackLayout.setItem( this.panel2 );
}
// Fallback to parent handler
return PendingDialog.super.prototype.getActionProcess.call( this, action );
};
// Override the getBodyHeight() method to specify a custom height (or don't to use the automatically generated height)
PendingDialog.prototype.getBodyHeight = function () {
return this.panel1.$element.outerHeight( true );
};
mw.PendingDialog = PendingDialog;
}( mediaWiki, jQuery ) );
var wikibase = wikibase || {};
wikibase.queryService = wikibase.queryService || {};
wikibase.queryService.api = wikibase.queryService.api || {};
wikibase.queryService.api.Sparql = ( function( $ ) {
'use strict';
var SPARQL_SERVICE_URI = 'https://query.wikidata.org/bigdata/namespace/wdq/sparql';
var ERROR_CODES = {
TIMEOUT: 10,
MALFORMED: 20,
SERVER: 30,
UNKNOWN: 100
};
var ERROR_MAP = {
'QueryTimeoutException: Query deadline is expired': ERROR_CODES.TIMEOUT,
'MalformedQueryException: ': ERROR_CODES.MALFORMED
};
/**
* SPARQL API for the Wikibase query service
*
* @class wikibase.queryService.api.Sparql
* @license GNU GPL v2+
*
* @author Stanislav Malyshev
* @author Jonas Kress
* @constructor
*
* @param {string} [serviceUri] Optional URI to the SPARQL service endpoint
*/
function SELF( serviceUri ) {
this._serviceUri = serviceUri || SPARQL_SERVICE_URI;
}
/**
* @property {Object}
*/
SELF.prototype.ERROR_CODES = ERROR_CODES;
/**
* @property {Number}
* @private
*/
SELF.prototype._serviceUri = null;
/**
* @property {Number}
* @private
*/
SELF.prototype._executionTime = null;
/**
* @property {Object}
* @private
*/
SELF.prototype._error = null;
/**
* @property {Number}
* @private
*/
SELF.prototype._resultLength = null;
/**
* @property {Object}
* @private
*/
SELF.prototype._rawData = null;
/**
* @property {string}
* @private
*/
SELF.prototype._queryUri = null;
/**
* Submit a query to the API
*
* @return {jQuery.Promise}
*/
SELF.prototype.queryDataUpdatedTime = function() {
// Cache the update time only for a minute
var deferred = $.Deferred(), query = encodeURI( 'prefix schema: <http://schema.org/> ' +
'SELECT * WHERE {<http://www.wikidata.org> schema:dateModified ?y}' ), url = this._serviceUri +
'?query=' + query + '&nocache=' + Math.floor( Date.now() / 60000 ), settings = {
headers: {
Accept: 'application/sparql-results+json'
}
};
$.ajax( url, settings )
.done(
function( data, textStatus, jqXHR ) {
if ( !data.results.bindings[0] ) {
deferred.reject();
return;
}
var updateDate = new Date(
data.results.bindings[0][data.head.vars[0]].value ), dateText = updateDate
.toLocaleTimeString( navigator.language, {
timeZoneName: 'short'
} ) +
', ' + updateDate.toLocaleDateString( navigator.language, {
month: 'short',
day: 'numeric',
year: 'numeric'
} );
var differenceInSeconds = Math
.round( ( new Date() - updateDate ) / 1000 );
deferred.resolve( dateText, differenceInSeconds );
} ).fail( function() {
deferred.reject();
} );
return deferred;
};
/**
* Submit a query to the API
*
* @param {string[]} query
* @return {jQuery.Promise} query
*/
SELF.prototype.query = function( query ) {
var self = this, deferred = $.Deferred(), settings = {
headers: {
Accept: 'application/sparql-results+json'
}
};
this._queryUri = this._serviceUri + '?query=' + encodeURIComponent( query );
this._executionTime = Date.now();
$.ajax( this._queryUri, settings ).done( function( data, textStatus, request ) {
self._executionTime = Date.now() - self._executionTime;
if ( typeof data.boolean === 'boolean' ) {
self._resultLength = 1;
} else {
self._resultLength = data.results.bindings.length || 0;
}
self._rawData = data;
deferred.resolve();
} ).fail( function( request, options, exception ) {
self._executionTime = null;
self._rawData = null;
self._resultLength = null;
self._generateErrorMessage( request, options, exception );
deferred.reject();
} );
return deferred;
};
/**
* Get execution time in ms of the submitted query
*/
SELF.prototype._generateErrorMessage = function( request, options, exception ) {
var error = {
code: ERROR_CODES.UNKNOWN,
message: null,
debug: request.responseText
};
if ( request.status === 0 || exception ) {
error.code = ERROR_CODES.SERVER;
error.message = exception.message;
}
try {//extract error from server response
var errorToMatch = error.debug.substring( error.debug
.indexOf( 'java.util.concurrent.ExecutionException:' ) );
for ( var errorKey in ERROR_MAP ) {
if ( errorToMatch.indexOf( errorKey ) !== -1 ) {
error.code = ERROR_MAP[ errorKey ];
error.message = null;
}
}
if ( error.code === ERROR_CODES.UNKNOWN || error.code === ERROR_CODES.MALFORMED ) {
error.message = error.debug
.match(
/(java\.util\.concurrent\.ExecutionException\:)+(.*)(Exception\:)+(.*)/ )
.pop().trim();
}
} catch ( e ) {
}
this._error = error;
};
/**
* Get execution time in seconds of the submitted query
*
* @return {Number}
*/
SELF.prototype.getExecutionTime = function() {
return this._executionTime;
};
/**
* Get error of the submitted query if it has failed
*
* @return {object}
*/
SELF.prototype.getError = function() {
return this._error;
};
/**
* Get result length of the submitted query if it has failed
*
* @return {Number}
*/
SELF.prototype.getResultLength = function() {
return this._resultLength;
};
/**
* Get query URI
*
* @return {string}
*/
SELF.prototype.getQueryUri = function() {
return this._queryUri;
};
/**
* Process SPARQL query result.
*
* @param {Object} data
* @param {Function} rowHandler
* @param {*} context
* @private
* @return {*} The provided context, modified by the rowHandler.
*/
SELF.prototype._processData = function( data, rowHandler, context ) {
var results = data.results.bindings.length;
for ( var i = 0; i < results; i++ ) {
var rowBindings = {};
for ( var j = 0; j < data.head.vars.length; j++ ) {
if ( data.head.vars[j] in data.results.bindings[i] ) {
rowBindings[data.head.vars[j]] = data.results.bindings[i][data.head.vars[j]];
} else {
rowBindings[data.head.vars[j]] = undefined;
}
}
context = rowHandler( rowBindings, context );
}
return context;
};
/**
* Encode string as CSV.
*
* @param {string} string
* @return {string}
*/
SELF.prototype._encodeCsv = function( string ) {
var result = string.replace( /"/g, '""' );
if ( /[",\n]/.test( result ) ) {
result = '"' + result + '"';
}
return result;
};
/**
* Get the raw result
*
* @return {Object} result
*/
SELF.prototype.getResultRawData = function() {
return this._rawData;
};
/**
* Get the result of the submitted query as CSV
*
* @return {string} csv
*/
SELF.prototype.getResultAsCsv = function() {
var self = this,
data = self._rawData,
output = data.head.vars.map( this._encodeCsv ).join( ',' ) + '\n';
output = this._processData( data, function( row, out ) {
var rowOut = '';
var first = true;
var rowCSV;
for ( var rowVar in row ) {
if ( row[rowVar] === undefined ) {
rowCSV = '';
} else {
rowCSV = self._encodeCsv( row[rowVar].value );
}
if ( !first ) {
rowOut += ',';
} else {
first = false;
}
rowOut += rowCSV;
}
rowOut += '\n';
return out + rowOut;
}, output );
return output;
};
/**
* Get the result of the submitted query as JSON
*
* @return {string}
*/
SELF.prototype.getResultAsJson = function() {
var output = [],
data = this._rawData;
output = this._processData( data, function( row, out ) {
var extractRow = {};
for ( var rowVar in row ) {
extractRow[rowVar] = ( row[rowVar] || {} ).value;
}
out.push( extractRow );
return out;
}, output );
return JSON.stringify( output );
};
/**
* Get the result of the submitted query as raw JSON
*
* @return {string}
*/
SELF.prototype.getResultAsAllJson = function() {
return JSON.stringify( this._rawData );
};
/**
* Render value as per http://www.w3.org/TR/sparql11-results-csv-tsv/#tsv
*
* @param {Object} binding
* @return {string}
*/
SELF.prototype._renderValueTSV = function( binding ) {
var value = binding.value.replace( /\t/g, '' );
switch ( binding.type ) {
case 'uri':
return '<' + value + '>';
case 'bnode':
return '_:' + value;
case 'literal':
var lvalue = JSON.stringify( value );
if ( binding['xml:lang'] ) {
return lvalue + '@' + binding['xml:lang'];
}
if ( binding.datatype ) {
if ( binding.datatype === 'http://www.w3.org/2001/XMLSchema#integer' ||
binding.datatype === 'http://www.w3.org/2001/XMLSchema#decimal' ||
binding.datatype === 'http://www.w3.org/2001/XMLSchema#double' ) {
return value;
}
return lvalue + '^^<' + binding.datatype + '>';
}
return lvalue;
}
return value;
};
/**
* Get the result of the submitted query as SPARQL TSV
*
* @return {string}
*/
SELF.prototype.getSparqlTsv = function() {
var self = this,
data = this._rawData,
output = data.head.vars.map( function( vname ) {
return '?' + vname;
} ).join( '\t' ) + '\n';
output = this._processData( data, function( row, out ) {
var rowOut = '';
var first = true;
var rowTSV;
for ( var rowVar in row ) {
if ( row[rowVar] === undefined ) {
rowTSV = '';
} else {
rowTSV = self._renderValueTSV( row[rowVar] );
}
if ( !first ) {
rowOut += '\t';
} else {
first = false;
}
rowOut += rowTSV;
}
rowOut += '\n';
return out + rowOut;
}, output );
return output;
};
/**
* Get the result of the submitted query as simplified TSV
*
* @return {string}
*/
SELF.prototype.getSimpleTsv = function() {
var data = this._rawData,
output = data.head.vars.join( '\t' ) + '\n';
output = this._processData( data, function( row, out ) {
var rowOut = '';
var first = true;
var rowTSV;
for ( var rowVar in row ) {
if ( row[rowVar] === undefined ) {
rowTSV = '';
} else {
rowTSV = row[rowVar].value.replace( /\t/g, '' );
}
if ( !first ) {
rowOut += '\t';
} else {
first = false;
}
rowOut += rowTSV;
}
rowOut += '\n';
return out + rowOut;
}, output );
return output;
};
return SELF;
}( jQuery ) );