Mvolz has submitted this change and it was merged.
Change subject: Adds GET endpoint /api, takes URL & DOI
......................................................................
Adds GET endpoint /api, takes URL & DOI
* New /api endpoint
* Takes GET requests
* Cache enabled (client, not server-side)
* Takes both URL and DOI
* Moved files to /lib/
* unshorten.js now follows all redirects
* distingush.js determines whether search
term is DOI, or URL as default
* Most request logic moved to request.js
* All responses now have request errors
* Previously if a request error, body
was sent with URL as title
* Now body is still sent, but with 520
error.
* 'mediawiki' format in /lib/zotero.js changed
* ISBN now list of isbns
* ISSN now list of issns
* README.md created
Change-Id: Ia4b58d4b9eda3871c239c50bbacdce814bb3c134
---
A README.md
A lib/distinguish.js
A lib/requests.js
R lib/scrape.js
A lib/unshorten.js
A lib/zotero.js
M localsettings.js.sample
M package.json
M server.js
A test_files/4_input.json
D unshorten.js
D zotero.js
12 files changed, 575 insertions(+), 313 deletions(-)
Approvals:
Mvolz: Verified; Looks good to me, approved
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab5ff25
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+#Citoid
+
+Citoid is a node.js service which currently locates citation data given a URL.
+
+[Citoid Documentation on mediawiki.org](https://www.mediawiki.org/wiki/Citoid)
diff --git a/lib/distinguish.js b/lib/distinguish.js
new file mode 100644
index 0000000..052860c
--- /dev/null
+++ b/lib/distinguish.js
@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+/**
+ * https://www.mediawiki.org/wiki/citoid
+ *
+ * Distinguishes between different search contents, such as
+ * URL or DOI, and sends to appropriate request handler.
+ */
+
+var request = require('request');
+var urlParse = require('url');
+var requestFromURL = require('./requests.js').requestFromURL;
+var requestFromDOI = require('./requests.js').requestFromDOI;
+
+/**
+ * Determine type of string (doi, url) and callback on correct gandler
+ * @param {[type]} searchString what the end user searched for
+ * @param {Function} callback callback(extractedValue, correctFunction)
+ */
+var distinguish = function (searchString, callback){
+ var search, match, reDOI, parsedURL;
+
+ searchString.trim();
+
+ search = searchString;
+
+ reDOI = new RegExp('\\b10[.][0-9]{4,}[//].*\\b');
+
+ match = search.match(reDOI);
+
+ if (match){
+ callback(match[0], requestFromDOI);
+ } else {
+ parsedURL = urlParse.parse(search);
+ if (!parsedURL.protocol){
+ search = 'http://'+ search;
+ callback(search, requestFromURL);
+ } else {
+ callback(search, requestFromURL); //assume url if not
doi
+ }
+ }
+};
+
+module.exports = {
+ distinguish: distinguish
+};
+
+/*Test methods in main */
+if (require.main === module) {
+
+ distinguish("example.com", function (extracted, selectedFcn){
+ console.log(extracted);
+ });
+}
\ No newline at end of file
diff --git a/lib/requests.js b/lib/requests.js
new file mode 100644
index 0000000..dfde7d2
--- /dev/null
+++ b/lib/requests.js
@@ -0,0 +1,118 @@
+#!/usr/bin/env node
+/**
+ * https://www.mediawiki.org/wiki/citoid
+ *
+ * Request functions for difference search types,
+ * such as URL or DOI
+ */
+
+var bunyan = require('bunyan');
+var log = bunyan.createLogger({name: "citoid"});
+var unshorten = require('./unshorten.js');
+var scrape = require('./scrape.js').scrape;
+var zoteroRequest = require('./zotero.js').zoteroRequest;
+var util = require('util');
+
+/**
+ * runner function to use for a url
+ * @param {[type]} requestedURL want metadata from this url
+ * @param {[type]} opts zoteroRequest options object
+ * @param {Function} callback callback (error, statusCode, body)
+ */
+
+var requestFromURL = function (requestedURL, opts, callback){
+ opts.zoteroURL = util.format(opts.zoteroURL, 'web'); //use web endpoint
+ zoteroRequest(requestedURL, opts, function(error, response, body){
+ log.info("Zotero request made for: " + requestedURL);
+ if (error) {
+ log.error(error);
+ log.info("Falling back on native scraper.");
+ scrape(requestedURL, function(error, body){
+ callback (error, 200, body);
+ });
+ } else if ( response ) {
+ //501 indicates no translator available
+ //this is common- can indicate shortened url
+ //or a website not specified in the translators
+ if (response.statusCode == 501){
+ log.info("Status Code from Zotero: " +
response.statusCode);
+ log.info("Looking for redirects...");
+ //try again following all redirects
+ //we don't do this initially because many sites
+ //will redirect this fcn to a log-in screen
+ unshorten(requestedURL, function(detected,
expandedURL) {
+ if (detected) {
+ log.info("Redirect detected to
"+ expandedURL);
+ zoteroRequest(expandedURL,
opts, function(error, response, body){
+ if (response && !error
&& response.statusCode == 200){
+
log.info("Successfully retrieved and translated body from Zotero");
+ callback(null,
200, body);
+ }
+ else {
+ log.info("No
Zotero response available; falling back on native scraper.");
+
scrape(requestedURL, function(error, body){
+
callback(error, 200, body);
+ });
+ }
+ });
+ }
+ else {
+ log.info("No redirect detected;
falling back on native scraper.");
+ scrape(requestedURL,
function(error, body){
+ callback(error, 200,
body);
+ });
+ }
+ });
+ }
+
+ else if (response.statusCode == 200){
+ log.info("Successfully retrieved and translated
body from Zotero");
+ callback (null, 200, body);
+ }
+ else { //other response codes such as 500, 300
+ log.info("Falling back on native scraper.");
+ scrape(requestedURL, function(error, body){
+ callback (error, 200, body);
+ });
+ }
+ } else {
+ log.info("Falling back on native scraper.");
+ scrape(requestedURL, function(error, body){
+ callback (error, 200, body);
+ });
+ }
+ });
+};
+
+/**
+ * runner function to use for a DOI
+ * @param {[type]} requestedDOI want metadata from this DOI
+ * @param {[type]} opts zoteroRequest options object
+ * @param {Function} callback callback (error, statusCode, body)
+ */
+
+var requestFromDOI = function (requestedDOI, opts, callback){
+ var doiLink = 'http://dx.doi.org/'+requestedDOI;
+ //TODO: optimise this (can skip some steps in requestFromURL)
+ requestFromURL(doiLink, opts, function(error, responseCode, body){
+ callback(error, responseCode, body);
+ });
+};
+
+/*Test methods in main */
+if (require.main === module) {
+ var opts = {
+ zoteroURL:"http://localhost:1969/%s",
+ sessionID: "123abc",
+ format: "zotero"
+ };
+
+ requestFromURL("http://example.com", opts, function (error, statusCode,
body){
+ console.log(body);
+ });
+}
+
+module.exports = {
+ requestFromURL: requestFromURL,
+ requestFromDOI: requestFromDOI
+};
\ No newline at end of file
diff --git a/scrape.js b/lib/scrape.js
similarity index 68%
rename from scrape.js
rename to lib/scrape.js
index a2731e7..8482571 100644
--- a/scrape.js
+++ b/lib/scrape.js
@@ -12,7 +12,7 @@
* Currently scrapes title only
* callback runs on list of json objs (var body)
* @param {String} url url to scrape
- * @param {Function} callback callback function
+ * @param {Function} callback callback(error, body)
*/
var scrape = function(url, callback){
@@ -37,16 +37,16 @@
request(
{
- url: url,
+ url: url,
headers: {'user-agent': 'Mozilla/5.0'},
- followAllRedirects: true
+ followAllRedirects: true
}, function(error, response, html){
- var json = {itemType: 'webpage', url: url, title: url};
+ var citation = {itemType: 'webpage', url: url, title:
url};
if (error || !response) {
console.log(error);
- callback([json]);
+ callback(error, [citation]);
return;
}
@@ -55,28 +55,29 @@
}
catch (e){
console.log('Could not load document: ' + e);
- callback([json]);
+ callback(error, [citation]);
}
- json.title = getTitle();
+ citation.title = getTitle();
// Access date on format YYYY-MM-DD
- json.accessDate = (new
Date()).toISOString().substring(0, 10);
+ citation.accessDate = (new
Date()).toISOString().substring(0, 10);
var parsedUrl = response.request.uri ?
response.request.uri : urlParse.parse(url);
- if (json.title && parsedUrl && parsedUrl.hostname) {
- json.publicationTitle = parsedUrl.hostname;
+ if (citation.title && parsedUrl && parsedUrl.hostname) {
+ citation.publicationTitle = parsedUrl.hostname;
}
- callback([json]);
+ callback(error, [citation]);
});
};
if (require.main === module) {
- var sampleUrl = 'http://example.com';
+ var sampleUrl = 'http://example,.com';
console.log('scrape fcn running on sample url:'+sampleUrl);
- scrape(sampleUrl, function(body){
+ scrape(sampleUrl, function(error, body){
+ console.log(error);
console.log(body);
});
}
diff --git a/lib/unshorten.js b/lib/unshorten.js
new file mode 100644
index 0000000..f71d25b
--- /dev/null
+++ b/lib/unshorten.js
@@ -0,0 +1,22 @@
+(function() {
+
+ var request = require('request');
+ var urlParse = require('url');
+
+ /**
+ * [unshorten description]
+ * @param {String} url url trying to be unshortened
+ * @param {Function} callback callback(detectedRedirect, url)
+ */
+ var unshorten = function (url, callback){
+ request(url, function (error, response){
+ if (response && !error && (url !==
response.request.href)){
+ callback(true, response.request.href);
+ }
+ else {
+ callback(false, url);
+ }
+ });
+ };
+ module.exports = unshorten;
+}());
diff --git a/lib/zotero.js b/lib/zotero.js
new file mode 100644
index 0000000..17db3d2
--- /dev/null
+++ b/lib/zotero.js
@@ -0,0 +1,239 @@
+#!/usr/bin/env node
+/**
+ * https://www.mediawiki.org/wiki/citoid
+ *
+ * Supplies methods to send requests to a Zotero server
+ */
+
+var request = require('request');
+
+/**
+ * Requests to Zotero server
+ * @param {String} requestedURL url being requested
+ * @param {Object} opts options for request
+ * @param {Function} callback callback(error, response, body)
+ */
+var zoteroRequest = function(requestedURL, opts, callback){
+ var options = {
+ url: opts.zoteroURL,
+ method: 'POST',
+ json: {
+ "url": requestedURL,
+ "sessionid": opts.sessionID
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ callback(error, response, modifyBody(requestedURL,
opts.format, body));
+ }
+ else {
+ callback(error, response, body);
+ }
+
+ });
+};
+
+/**
+ * Modify body of a zotero or other response body
+ * @param {String} url original uri requested
+ * @param {String} format 'mediawiki', 'mwDeprecated', 'zotero', etc.
+ * @param {Object} body JSON request body
+ * @return {Object} JSON request body
+ */
+var modifyBody = function(url, format, body){
+ var formatFcns = {
+ 'mwDeprecated':convertToMWDeprecated,
+ 'mediawiki':convertToMediawiki,
+ 'zotero':convertToZotero
+ },
+ convert = formatFcns[format];
+
+ //if format is not available, use zotero as default- may want to switch
to returning error instead
+ if (convert){
+ return convert(url, body);
+ }
+ else {
+ return convertToZotero(url, body);
+ }
+};
+
+/*Specific conversion methods*/
+var convertToZotero = function(url, body){
+ citation = body[0][0];
+
+ fixAccessDate(citation);
+ fixURL(url, citation);
+
+ return [[citation]];
+};
+
+var convertToMediawiki = function(url, body){
+
+ citation = body[0][0];
+
+ replaceCreators(citation);
+ addPMID(citation);
+ fixURL(url, citation);
+ fixAccessDate(citation);
+ fixISBN(citation);
+ fixISSN(citation);
+
+ return [citation];
+};
+
+var convertToMWDeprecated = function(url, body){
+ var zotCreators, issn,
+ creatorTypeCount = {},
+ citation = body[0][0];
+
+ //flattens creator field
+ if (citation.creators) {
+ zotCreators = citation.creators;
+
+ for (var z in zotCreators){
+ creatorFieldName = zotCreators[z].creatorType;
+ if (creatorTypeCount[creatorFieldName]){
+ creatorTypeCount[creatorFieldName] += 1;
+ }
+ else {
+ creatorTypeCount[creatorFieldName] = 1;
+ }
+ //Appends number to name, i.e. author -> author1
+ creatorFieldName +=
(parseInt(creatorTypeCount[creatorFieldName]));
+
+ citation[creatorFieldName + "-first"] =
zotCreators[z].firstName;
+ citation[creatorFieldName + "-last"] =
zotCreators[z].lastName;
+ }
+ delete citation.creators; //remove creators field
+ }
+
+ fixURL(url, citation);
+ addPMID(citation);
+
+ //In some cases where two ISSNs, return first found
+ //If no match, leave field as is for user to correct
+ if (citation.ISSN){
+ issn = citation.ISSN;
+ reISSN = new RegExp('\\d{4}\\-\\d{3}[\\dX]');
+ match = issn.match(reISSN);
+ if (match) {
+ citation.ISSN = match[0];
+ }
+ }
+
+ return [citation];
+};
+
+/*Methods for particular fields-
+* Mediawiki format if not otherwise specified
+*/
+
+var replaceCreators = function(citation){
+ if (citation.creators) {
+ zotCreators = citation.creators;
+
+ creatorMap = {};
+
+ for (var z in zotCreators){
+ var creatorArray,
+ creatorFieldName = zotCreators[z].creatorType;
+
+ if (!citation[creatorFieldName]){
+ creatorArray = [];
+ citation[creatorFieldName]= creatorArray;
+ }
+
+
citation[creatorFieldName].push([zotCreators[z].firstName,
zotCreators[z].lastName]);
+ }
+
+ delete citation.creators; //remove creators field
+ }
+};
+
+var addPMID = function(citation){
+ //get pmid out of extra fields
+ if (citation.extra){
+ var extraFields = citation.extra.split('\n');
+ for (var f in extraFields){
+ //could add them all, but let's not do this in case of
conflicting fields
+ var keyValue = extraFields[f].split(': ');
+ if (keyValue[0] === 'PMID'){
+ citation[keyValue[0]] = keyValue[1].trim();
+ }
+ }
+ }
+ //TODO: if no pmid available from zotero output, get one from doi using
api: http://www.ncbi.nlm.nih.gov/pmc/tools/id-converter-api/
+};
+
+var fixURL = function(url, citation){
+ if (!citation.url){
+ citation.url = url;
+ }
+};
+
+var fixAccessDate = function(citation){
+ if (!citation.accessDate || (citation.accessDate ==
"CURRENT_TIMESTAMP")){
+ citation.accessDate = (new Date()).toISOString().substring(0,
10);
+ }
+};
+
+var fixISSN = function(citation){
+ var match, i,
+ issn = citation.ISSN;
+
+ reISSN = new RegExp('\\d{4}\\-\\d{3}[\\dX]', 'g');
+
+ if (issn){
+ issn.trim();
+ match = issn.match(reISSN);
+ if (match) {
+ citation.ISSN = [];
+ for (i in match){
+ citation.ISSN.push(match[i]);
+ }
+ } else{
+ citation.ISSN = [issn]; //wraps issn field in array in
case of false negatives
+ }
+ }
+};
+
+var fixISBN = function(citation){
+ var match, i,
+ isbn = citation.ISBN;
+
+ reISBN = new RegExp('((978[\\--– ])?[0-9][0-9\\--– ]{10}[\\--–
][0-9xX])|((978)?[0-9]{9}[0-9Xx])', 'g');
+
+ if (isbn) {
+ isbn.trim();
+ match = isbn.match(reISBN);
+ if (match) {
+ citation.ISBN = [];
+ for (i in match){
+ citation.ISBN.push(match[i]);
+ }
+ } else {
+ citation.ISBN = [isbn]; //wraps isbn field in array in
case of false negatives
+ }
+ }
+};
+
+/*Test response alterations without having to use server*/
+var testJSON = function(){
+ var sampleJSON = require("../test_files/3_input.json");
+ console.log("before:");
+ console.log(JSON.stringify(sampleJSON));
+ console.log("after:");
+
console.log(JSON.stringify(modifyBody("http://example.com","mediawiki",sampleJSON)));
+};
+
+/*Test methods in main */
+if (require.main === module) {
+ testJSON();
+}
+
+/*Exports*/
+module.exports = {
+ zoteroRequest: zoteroRequest
+};
+
diff --git a/localsettings.js.sample b/localsettings.js.sample
index 0a3d93b..bb376fa 100644
--- a/localsettings.js.sample
+++ b/localsettings.js.sample
@@ -8,9 +8,6 @@
var CitoidConfig = {
- //Set your WorldCat API key here
- wskey : false,
-
// Enable debug mode (prints extra debugging messages)
debug : true,
diff --git a/package.json b/package.json
index 659a421..bc76360 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"async" : "latest",
"bluebird" : "latest",
"body-parser" : "latest",
+ "bunyan" : "latest",
"cheerio" : "latest",
"express" : "latest",
"path": "latest",
diff --git a/server.js b/server.js
index 881ffff..aba4de1 100644
--- a/server.js
+++ b/server.js
@@ -2,14 +2,14 @@
/**
* https://www.mediawiki.org/wiki/citoid
*/
+
/*external modules*/
-var express = require('express');
-var request = require('request');
var bodyParser = require('body-parser');
+var bunyan = require('bunyan');
+var express = require('express');
+var path = require('path');
var urlParse = require('url');
var util = require('util');
-var path = require('path');
-var unshorten = require('./unshorten.js');
var opts = require('yargs')
.usage('Usage: $0 [-c configfile|--config=configfile]')
.default({
@@ -18,9 +18,8 @@
.alias( 'c', 'config' );
var argv = opts.argv;
-/*internal modules*/
-var zoteroRequest = require('./zotero.js').zoteroRequest;
-var scrape = require('./scrape.js').scrape;
+var distinguish = require('./lib/distinguish.js').distinguish;
+var requestFromURL = require('./lib/requests.js').requestFromURL;
/* import local settings*/
var settingsFile = path.resolve(process.cwd(), argv.c);
@@ -29,18 +28,15 @@
var citoidInterface = CitoidConfig.citoidInterface;
var zoteroPort = CitoidConfig.zoteroPort;
var zoteroInterface = CitoidConfig.zoteroInterface;
-var wskey = CitoidConfig.wskey;
var debug = CitoidConfig.debug;
var allowCORS = CitoidConfig.allowCORS;
//url base which allows further formatting by adding a single endpoint, i.e.
'web'
-var zoteroURL = util.format('http://%s:%s/%s', zoteroInterface,
zoteroPort.toString());
-
-/*testing variables*/
-var testSessionID = "abc123";
+var zoteroURL = util.format('http://%s:%s/%s', zoteroInterface,
zoteroPort.toString());
//Init citoid webserver
var citoid = express();
+var log = bunyan.createLogger({name: "citoid"});
//SECURITY WARNING: ALLOWS ALL REQUEST ORIGINS
//change allowCORS in localsettings.js
@@ -52,8 +48,11 @@
citoid.use(bodyParser.json());
citoid.use(bodyParser.urlencoded({extended: false}));
+//citoid.use(bunyan());
+citoid.use(express.static('api')); //cache api pages
/*Landing page*/
+/*jshint multistr: true */
citoid.get('/', function(req, res){
res.setHeader("Content-Type", "text/html");
res.send('<!DOCTYPE html>\
@@ -77,89 +76,90 @@
/*Endpoint for retrieving citations in JSON format from a URL*/
citoid.post('/url', function(req, res){
- var format = req.body.format,
+ var opts, parsedURL,
+ format = req.body.format,
requestedURL = req.body.url,
- zoteroURLWeb = util.format(zoteroURL, 'web');
+ sessionID = "123abc";
+
+ log.info(req);
//temp backwards compatibility
if (!format){
- format = 'mwDeprecated';
+ format = 'mwDeprecated';
}
res.type('application/json');
- //TODO: fix for async error catching
- try {
- var parsedURL = urlParse.parse(requestedURL);
- //defaults to http if no protocol specified.
- if (!parsedURL.protocol){
- requestedURL = 'http://'+ urlParse.format(parsedURL);
- }
- else {requestedURL = urlParse.format(parsedURL);}
+ parsedURL = urlParse.parse(requestedURL);
+ //defaults to http if no protocol specified.
+ if (!parsedURL.protocol){
+ requestedURL = 'http://'+ urlParse.format(parsedURL);
}
- catch (e){
- console.log(e);
- }
+ else {requestedURL = urlParse.format(parsedURL);}
- //make things work with new format for now..
- //format = 'mediawiki';
+ opts = {
+ zoteroURL:zoteroURL,
+ sessionID: sessionID,
+ format: format
+ };
- //Request from Zotero and set response
- zoteroRequest(zoteroURLWeb, requestedURL, testSessionID, format,
function(error, response, body){
- console.log("Request made for: " + requestedURL);
- if (response) {
- if (!error) {
- //501 indicates no translator availabe
- //this is common- can indicate shortened url
- //or a website not specified in the translators
- if (~[500, 501].indexOf(response.statusCode)){
- //try again with unshortened url
- //we don't do this initially because
many sites
- //will redirect this fcn to a log-in
screen
- unshorten(requestedURL,
function(expandedURL) {
- zoteroRequest(zoteroURLWeb,
expandedURL, testSessionID, format,
- function(error,
response, body){
- if (response){
- //if still no
translator, or translation fails,
- //send to naive
scraper
- if (~[500,
501].indexOf(response.statusCode)){
-
scrape(requestedURL, function(body){
-
res.statusCode = 200;
-
res.json(body);
- });
- }
- else {
-
res.statusCode = 200;
-
res.json(body);
- }
- }
- });
- });
- }
- else {
- res.statusCode = response.statusCode;
- res.json(body);
- }
- }
- else {
- res.statusCode = 500;
- res.json("Internal server error");
- console.log(error);
- }
+ requestFromURL(requestedURL, opts, function(error, responseCode, body){
+ if (!error){
+ res.statusCode = responseCode;
+ res.send(body);
}
else {
- //no response
- var message = "Server at "+zoteroURL+" does not appear
to be running.";
- res.statusCode = 500;
- res.json("Internal server error");
- console.log(message);
+ res.statusCode = 520;
+ res.send(body);
}
});
});
+/**Endpoint for retrieving citations based on search term (URL,DOI)*/
+citoid.get('/api', function(req, res){
+ var opts, dSearch,
+ format = req.query.format,
+ search = req.query.search;
+
+ log.info(req);
+
+ if (!search){
+ res.statusCode = 400;
+ res.setHeader("Content-Type", "text/plain");
+ res.send("No 'search' value specified\n");
+ } else if(!format){
+ res.statusCode = 400;
+ res.setHeader("Content-Type", "text/plain");
+ res.send("No 'format' value specified\nOptions are
'mediawiki','zotero'");
+ } else {
+
+ dSearch = decodeURIComponent(search); //decode urlencoded
search string
+
+ distinguish(dSearch, function(extractedID, runnerFunction){
+ opts = {
+ zoteroURL:zoteroURL,
+ sessionID:"123abc",
+ format:format,
+ identifier:extractedID
+ };
+
+ runnerFunction(extractedID, opts, function(error,
responseCode, body){
+ if (!error){
+ res.statusCode = responseCode;
+ res.send(body);
+ }
+ else {
+ res.statusCode = 520; //Server at
requested location not available
+ res.send(body);
+ }
+ });
+ });
+ }
+});
+
citoid.listen(citoidPort);
-console.log('Server running on http://localhost:'+citoidPort);
+log.info('Server started on http://localhost:'+citoidPort);
/*Exports*/
exports = module.exports = citoid;
diff --git a/test_files/4_input.json b/test_files/4_input.json
new file mode 100644
index 0000000..dd4146a
--- /dev/null
+++ b/test_files/4_input.json
@@ -0,0 +1,47 @@
+[
+ [
+ {
+ "itemKey": "RNW2N68C",
+ "itemVersion": 0,
+ "itemType": "journalArticle",
+ "creators": [
+ {
+ "firstName": "Ribhi",
+ "lastName": "Hazin",
+ "creatorType": "author"
+ },
+ {
+ "firstName": "Tarek I. Abu-Rajab",
+ "lastName": "Tamimi",
+ "creatorType": "author"
+ },
+ {
+ "firstName": "Jamil Y.",
+ "lastName": "Abuzetun",
+ "creatorType": "author"
+ },
+ {
+ "firstName": "Nizar N.",
+ "lastName": "Zein",
+ "creatorType": "author"
+ }
+ ],
+ "tags": [],
+ "title": "Recognizing and treating cutaneous signs of liver
disease",
+ "date": "10/01/2009",
+ "DOI": "10.3949/ccjm.76A.08113",
+ "language": "en",
+ "publicationTitle": "Cleveland Clinic Journal of Medicine",
+ "journalAbbreviation": "Cleveland Clinic Journal of Medicine",
+ "volume": "76",
+ "issue": "10",
+ "url": "http://www.ccjm.org/content/76/10/599",
+ "pages": "599-606",
+ "ISSN": "0891-1150, 1939-2869",
+ "extra": "PMID: 19797460",
+ "libraryCatalog": "www.ccjm.org",
+ "accessDate": "CURRENT_TIMESTAMP",
+ "abstractNote": "Cutaneous changes may be the first clue that a
patient has liver disease. Recognizing these signs is crucial to diagnosing
liver conditions early. Here we describe the spectrum of skin manifestations
that may be found in various liver diseases.\nKey points\nPruritus due to liver
disease is particularly resistant to therapy. Cholestyramine (Questran) 4
g/day, gradually increased to 24 g/day, is one option. If the pruritus does not
respond or the patient cannot tolerate cholestyramine, rifampin (Rifadin) can
be tried.\nSpider angiomas, Bier spots, and “paper-money” skin are all
superficial vascular problems that may be related to liver disease.\nCutaneous
lesions often accompany alcoholic cirrhosis and have been detected in up to 43%
of people with chronic alcoholism. The combination of spider angiomas, palmar
erythema, and Dupuytren contracture is common in alcoholic cirrhosis.\nAlthough
porphyria cutanea tarda is associated with liver disease in general, recent
studies show that patients with hepatitis C are at particularly high risk."
+ }
+ ]
+]
\ No newline at end of file
diff --git a/unshorten.js b/unshorten.js
deleted file mode 100644
index 017793e..0000000
--- a/unshorten.js
+++ /dev/null
@@ -1,16 +0,0 @@
-(function() {
-
- var request = require('request');
-
- var unshorten = function (url, callback){
- request(url, function (error, response, body) {
- if (!error && ~[301, 302].indexOf(response.statusCode))
{
- callback(response.headers.location);
- }
- else {
- callback(url);
- }
- });
- };
- module.exports = unshorten;
-}());
diff --git a/zotero.js b/zotero.js
deleted file mode 100644
index 5b917ba..0000000
--- a/zotero.js
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/usr/bin/env node
-/**
- * https://www.mediawiki.org/wiki/citoid
-*
-* Supplies methods to send requests to a Zotero server
- */
-
-var request = require('request');
-
-var zoteroRequest = function(zoteroURL, requestedURL, sessionID, format,
callback){
- var options = {
- url: zoteroURL,
- method: 'POST',
- json: {
- "url": requestedURL,
- "sessionid": sessionID
- }
- };
-
- request(options, function (error, response, body) {
- if (!error && response.statusCode == 200) {
- //modify body only if response is okay
- callback(error, response, modifyBody(requestedURL,
format, body));
- }
- else {
- callback(error, response, body);
- }
-
- });
-};
-
-/*Converts Zotero body into appropriate format*/
-var modifyBody = function(url, format, body){
- console.log(format);
- var formatFcns = {
- 'mwDeprecated':convertToMWDeprecated,
- 'mediawiki':convertToMediawiki,
- 'zotero':convertToZotero
- },
- convert = formatFcns[format];
-
- //if format is not available, use zotero as default- may want to switch
to returning error instead
- if (convert){
- return convert(url, body);
- }
- else {
- return convertToZotero(url, body);
- }
-};
-
-/*Specific conversion methods*/
-var convertToZotero = function(url, body){
- return body;
-};
-
-var convertToMediawiki = function(url, body){
- var citation, zotCreators, creatorMap,
- creatorTypeCount = {};
-
- //hack in most cases body will be an array, but in some will be an
array of arrays
- if (!(body[0] instanceof Array)){
- citation = body[0];
- }
- else if (!(body[0][0] instanceof Array)){
- citation = body[0][0];
- }
- else {
- return body;
- }
-
- if (citation.creators) {
- zotCreators = citation.creators;
-
- creatorMap = {};
-
- for (var z in zotCreators){
- var creatorArray,
- creatorFieldName = zotCreators[z].creatorType;
-
- if (!citation[creatorFieldName]){
- creatorArray = [];
- citation[creatorFieldName]= creatorArray;
- }
-
-
citation[creatorFieldName].push([zotCreators[z].firstName,
zotCreators[z].lastName]);
- }
-
- delete citation.creators; //remove creators field
- }
-
- //some zotero requests come back without the url field filled in
- if (!citation.url){
- citation.url = url;
- }
-
- //get pmid out of extra fields
- if (citation.extra){
- var extraFields = citation.extra.split('\n');
- for (var f in extraFields){
- //could add them all, but let's not do this in case of
conflicting fields
- var keyValue = extraFields[f].split(': ');
- if (keyValue[0] === 'PMID'){
- citation[keyValue[0]] = keyValue[1].trim();
- }
- }
- }
-
- return [citation];
-
-};
-
-var convertToMWDeprecated = function(url, body){
- var citation, zotCreators,
- creatorTypeCount = {};
-
- //hack- in most cases body will be an array, but in some will be an
array of arrays
- if (!(body[0] instanceof Array)){
- citation = body[0];
- }
- else if (!(body[0][0] instanceof Array)){
- citation = body[0][0];
- }
- else {
- return body;
- }
-
- if (citation.creators) {
- zotCreators = citation.creators;
-
- for (var z in zotCreators){
- creatorFieldName = zotCreators[z].creatorType;
- if (creatorTypeCount[creatorFieldName]){
- creatorTypeCount[creatorFieldName] += 1;
- }
- else {
- creatorTypeCount[creatorFieldName] = 1;
- }
- //Appends number to name, i.e. author -> author1
- creatorFieldName +=
(parseInt(creatorTypeCount[creatorFieldName]));
-
- citation[creatorFieldName + "-first"] =
zotCreators[z].firstName;
- citation[creatorFieldName + "-last"] =
zotCreators[z].lastName;
- }
- delete citation.creators; //remove creators field
- }
- //some zotero requests come back without the url field filled in
- if (!citation.url){
- citation.url = url;
- }
- //get pmid out of extra fields
- if (citation.extra){
- var extraFields = citation.extra.split('\n');
- for (var f in extraFields){
- //could add them all, but let's not do this in case of
conflicting fields
- var keyValue = extraFields[f].split(': ');
- if (keyValue[0] === 'PMID'){
- citation[keyValue[0]] = keyValue[1].trim();
- }
- }
- }
- return [citation];
-};
-
-/*Test server fcns*/
-var testServer = function(){
- var zoteroURL = 'http://localhost:1969/web', //edit this to your own
endpoint
- testURL =
"http://www.tandfonline.com/doi/abs/10.1080/15424060903167229", //URL that
works with Zotero
- //testURL =
"http://books.google.co.uk/books?hl=en&lr=&id=7lueAgAAQBAJ&oi=fnd&pg=PR5&dq=mediawiki&ots=-Z0o2LCgao&sig=IGHnyWEiNiNvPyXeyCuOcdvi15s#v=onepage&q=mediawiki&f=false",
//url that doesn't work with zotero
- testSessionID = 'abc123';
- //format = 'mwDeprecated';
- format = 'mediawiki';
-
- zoteroRequest(zoteroURL, testURL, testSessionID, format,
function(error, response, body){
- if (response) {
- if (!error && response.statusCode == 200) {
- console.log(body);
- }
- else if(response.statusCode == 501){
- console.log(body);
- }
- }
- else {console.log("Server at "+zoteroURL+" does not appear to
be running.");}
- });
-};
-
-/*Test response alterations without having to use server*/
-var testJSON = function(){
- var sampleJSON = require("./sampleZoteroResponseBody.json");
- console.log("before:");
- console.log(JSON.stringify(sampleJSON));
- console.log("after:");
- console.log(JSON.stringify(modifyBody(sampleJSON)));
-};
-
-/*Test methods in main */
-if (require.main === module) {
- //testJSON();
- testServer();
-}
-
-/*Exports*/
-module.exports = {
- zoteroRequest: zoteroRequest
-};
-
--
To view, visit https://gerrit.wikimedia.org/r/169690
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia4b58d4b9eda3871c239c50bbacdce814bb3c134
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/services/citoid
Gerrit-Branch: master
Gerrit-Owner: Mvolz <[email protected]>
Gerrit-Reviewer: Mvolz <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits