wiki-reference

Позволяет генерировать ссылки в формате {{статья}} и {{книга}} для ру-вики

Ajankohdalta 20.8.2015. Katso uusin versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        wiki-reference
// @namespace   torvin
// @include     https://www.ncbi.nlm.nih.gov/pubmed/*
// @include     http://www.ncbi.nlm.nih.gov/pubmed/*
// @include     http://adsabs.harvard.edu/abs/*
// @include     http://adsabs.harvard.edu/doi/*
// @include     http://ufn.ru/ru/articles/*
// @include     http://books.google.*/books*
// @include     https://books.google.*/books*
// @include     http://www.sciencedirect.com/science/article/*
// @include     http://gen.lib.rus.ec/scimag/*
// @include     http://onlinelibrary.wiley.com/doi/*
// @include     http://www.jstor.org/stable/*
// @include     http://www.jstor.org/discover/*
// @include     http://projecteuclid.org/euclid.*
// @include     https://projecteuclid.org/euclid.*
// @include     http://link.springer.com/*
// @include     http://www.mathnet.ru/php/archive.phtml*wshow=paper*
// @require     https://code.jquery.com/jquery-2.1.1.min.js
// @version     1.7
// @description Позволяет генерировать ссылки в формате {{статья}} и {{книга}} для ру-вики
// @grant       GM_xmlhttpRequest
// @grant       GM_setClipboard
// ==/UserScript==

const templates = {
	article: 'статья',
	book: 'книга',
};

const templateAdditionalArgs = {
	'статья': [ 'ref', 'archiveurl', 'archivedate' ],
	'книга': [ 'ref' ],
};

var Template = function(name) {
	var attrs = [];

	this.add = function(name, value) {
		attrs.push({
			name: name, 
			value: value
		});
		return this;
	};

	this.getName = function() {
		return name;
	}

	var getAttr = function(x) { 
		return "|" + x.name + (x.value === undefined ? "" : " = " + x.value);
	};

	this.toWiki = function() {
		if (attrs.length == 1)
			return "{{" + name + getAttr(attrs[0]) + "}}";
		else
			return "{{" + name + "\n" + attrs.map(function(x) { return " " + getAttr(x)}).join("\n") + "\n}}";
	};
};

var getText = function(node) {
	return node instanceof Element ? node.textContent : node;
};

var clone = function(obj) {
	var target = {};
	for (var i in obj) {
		var value = obj[i];
		if (value instanceof Function)
			;
		else if (typeof value == 'string')
			;
		else
			value = clone(value);
		target[i] = value;
	}
	return target;
}

var getWpFromXml = function(name, rules, xml) {
	var article = new Template(name);

	for(var name in rules) {
		var rule = rules[name];
		article.add(name, rule.const ||
			Array.slice(xml.querySelectorAll(rule.selector)).map(function(node) {
				if (rule.map)
					node = rule.map(node);
				return Array.isArray(node) ? node.map(getText) : getText(node);
			}).map(function(item) {
				return rule.mapText ? rule.mapText(item) : item;
			}).join(rule.separator || ', ')
		);
	}

	return getRef(article);
};

var Parser = function(sourceText, index, tokens) {
	var _match;
	var _isEof;

	//var _tokenRegex = /(\s*)(\{|\}|,|=|\\\W|\\[\w]+|[^\{\},\\=\s])/g;
	var _tokenRegex = new RegExp("(\\s*)(" + tokens.map(function(x) { return x.source || x }).join('|') + ")", 'g');
	_tokenRegex.lastIndex = index;

	var getToken = function() {
		if (_isEof)
			throw new Error("EOF");

		var index = _tokenRegex.lastIndex;

		var res = _tokenRegex.exec(sourceText);
		if (!res) {
			_isEof = true;
			return null;
		}
		_match = {
			match: res[0],
			token: res[2],
			space: res[1].replace(/\s+/g, ' '),
			index: index,
		}
	}

	this.matchAny = function() {
		var res = _match;
		getToken();
		return res;
	}

	this.match = function(str) {
		if (_match.token !== str)
			throw new Error("Parser error at pos " + _tokenRegex.lastIndex + ". Expected '" + str + "', found '" + _match.token + "'.");
		return this.matchAny();
	}

	this.matchIgnoreCase = function(str) {
		if (_match.token.toUpperCase() !== str.toUpperCase())
			throw new Error("Parser error at pos " + _tokenRegex.lastIndex + ". Expected '" + str + "', found '" + _match.token + "'.");
		return this.matchAny();
	}

	this.matchAnyIgnoreCase = function(strs) {
		if (strs.every(function(str) { return _match.token.toUpperCase() !== str.toUpperCase(); }))
			throw new Error("Parser error at pos " + _tokenRegex.lastIndex + ". Expected any of: '" + strs.join("', '") + "', found '" + _match.token + "'.");
		return this.matchAny();
	}

	this.matchNot = function(strs) {
		if (strs.indexOf(_match.token) != -1)
			throw new Error("Parser error at pos " + _tokenRegex.lastIndex + ". Unexpected '" + _match.token + "'.");
		return this.matchAny();
	}

	this.index = function() {
		return _match.index;
	}

	this.isEof = () => _isEof;

	Object.defineProperty(this, "token", { get: function() { return _match.token; }});

	getToken();
}

var Bibtex = function(sourceText, index) {
	var _plainText = /[^\{\},\\=\s"\$]+/;
	const _tokens = [
		/\{/,
		/\}/,
		/"/,
		/,/,
		/=/,
		/@/,
		/\$/,
		/\\\W/,
		/\\[\w]+/,
		_plainText,
	];

	var _parser = new Parser(sourceText, index || 0, _tokens);

	var entry = function() {
		_parser.match("{");
		var id = fieldValue();
		_parser.match(",");
		var f = fields();
		_parser.match("}");

		f.bibcode = id;
		return f;
	}

	var comment = function() {
		_parser.match("{");
		var text = fieldValue();
		_parser.match("}");
		return { comment: text };
	}

	var type = function(entryTypes) {
		_parser.match('@');
		var token = _parser.token;
		if (entryTypes.length)
			_parser.matchAnyIgnoreCase(entryTypes);
		else
			_parser.matchAny();
		return token;
	}

	var fields = function() {
		var res = {};
		for(;;) {
			var f = field();
			res[f.name] = f.value;
			if (_parser.token !== ",") break;
			_parser.match(",");
			if (_parser.token === "}") break;
		}
		return res;
	}

	var quoted = function() {
		return _parser.match("{").space + quotedValue() + _parser.match("}").space;
	}

	var diacritics = {
		'`': '\u0300',
		'\'': '\u0301',
		'^': '\u0302',
		'~': '\u0303',
		'=': '\u0304',
		'u': '\u0306',
		'.': '\u0307',
		'"': '\u0308',
		'r': '\u030a',
		'H': '\u030b',
		'v': '\u030c',
		'c': '\u0327',
		'k': '\u0328',
		'd': '\u0323',
		'b': '\u0331',
		't': '\u0361',
	};

	var ligatures = {
		'L': '\u0141',
		'l': '\u0142',
		'AA': '\u00c5',
		'aa': '\u00e5',
		'AE': '\u00c6',
		'ae': '\u00e6',
		'O': '\u00d8',
		'o': '\u00f8',
		'OE': '\u0152',
		'oe': '\u0153',
		'i': '\u0131',
		'j': '\u0237',
		'ss': '\u00df',
	};

	var entity = function() {
		if (_parser.token[0] != '\\')
			throw new Error("Expected entity, found " + _parser.token);

		var cmd = _parser.matchAny().token.substr(1);

		var value = ligatures[cmd];
		if (value)
			return value;

		value = diacritics[cmd];
		if (value)
			return fieldValue() + value;

		return cmd;
	}

	var quotedValue = function() {
		var res = "";
		for(;;) {
			if (_parser.token === "{")
				res += quoted();
			else if (_parser.token === "}")
				break;
			else if (_parser.token[0] === '\\')
				res += entity();
			else
				res += plainText();
		}
		return res;
	}

	var plainText = function() {
		return _parser.matchAny().match.replace(/---?/g, '—').replace(/~/g, ' ');
	}

	var fieldValue = function() {
		var res = "";
		for(;;) {
			if (_parser.token == "{")
				res += quoted();
			else if (_parser.token == '"')
				res += doubleQuoted();
			else if (_parser.token[0] === '\\')
				res += entity();
			else if (_parser.token === '$')
				res += math();
			else if (_plainText.test(_parser.token))
				res += plainText();
			else
				break;
		}
		return res.trim();
	}

	var amsFieldValue = function() {
		var res = "";
		for(;;) {
			if (_parser.isEof())
				break;
			else if (_parser.token == "{")
				res += quoted();
			else if (_parser.token == '"')
				res += doubleQuoted();
			else if (_parser.token[0] === '\\')
				res += entity();
			else if (_parser.token === '$')
				res += math();
			else
				res += plainText();
		}
		return res.trim();
	}

	var math = function() {
		var before = _parser.match('$').space;
		var res = '';
		for(;;) {
			if (_parser.token === '$')
				break;
			else
				res += _parser.matchAny().match;
		}
		return before + '<math>' + res + '</math>' + _parser.match('$').space;
	}

	var doubleQuoted = function() {
		return _parser.match('"').space + doubleQuotedValue() + _parser.match('"').space;
	}

	var doubleQuotedValue = function() {
		var res = "";
		for(;;) {
			if (_parser.token === '"')
				break;
			else if (_parser.token[0] === '\\')
				res += entity();
			else
				res += plainText();
		}
		return res;
	}

	var field = function() {
		var name = fieldValue();
		_parser.match("=");
		var value = fieldValue();

		return {
			name: name,
			value: value
		}
	}

	this.index = function() {
		return _parser.index();
	}

	this.parse = function(entryTypes) {
		if (!entryTypes)
			entryTypes = []
		else if (typeof entryTypes == 'string' || entryTypes instanceof String)
			entryTypes = [ entryTypes ];
		var realType = type(entryTypes);

		if (realType.toLowerCase() == 'comment')
			var result = comment();
		else
			var result = entry();

		result.type = realType;
		return result;
	}

	this.parseAmsFieldValue = function() {
		return amsFieldValue();
	}
}

var bibtexBase = {
	'автор': {
		selector: 'author',
		map: (text) => authorsToWiki(getAuthors(text)),
	},
	'заглавие': {
		selector: 'title',
		map: function(text) {
			return text.replace(/^"|"$/g, '')
		}
	},
	'издание': { 
		selector: 'journal',
		map: function(text) {
			return {
				aj: 'Astronomical Journal',
				actaa: 'Acta Astronomica',
				araa: 'Annual Review of Astron and Astrophys',
				apj: 'Astrophysical Journal',
				apjl: 'Astrophysical Journal, Letters',
				apjs: 'Astrophysical Journal, Supplement',
				ao: 'Applied Optics',
				apss: 'Astrophysics and Space Science',
				aap: 'Astronomy and Astrophysics',
				aapr: 'Astronomy and Astrophysics Reviews',
				aaps: 'Astronomy and Astrophysics, Supplement',
				azh: 'Astronomicheskii Zhurnal',
				baas: 'Bulletin of the AAS',
				caa: 'Chinese Astronomy and Astrophysics',
				cjaa: 'Chinese Journal of Astronomy and Astrophysics',
				icarus: 'Icarus',
				jcap: 'Journal of Cosmology and Astroparticle Physics',
				jrasc: 'Journal of the RAS of Canada',
				memras: 'Memoirs of the RAS',
				mnras: 'Monthly Notices of the RAS',
				na: 'New Astronomy',
				nar: 'New Astronomy Review',
				pra: 'Physical Review A: General Physics',
				prb: 'Physical Review B: Solid State',
				prc: 'Physical Review C',
				prd: 'Physical Review D',
				pre: 'Physical Review E',
				prl: 'Physical Review Letters',
				pasa: 'Publications of the Astron. Soc. of Australia',
				pasp: 'Publications of the ASP',
				pasj: 'Publications of the ASJ',
				rmxaa: 'Revista Mexicana de Astronomia y Astrofisica',
				qjras: 'Quarterly Journal of the RAS',
				skytel: 'Sky and Telescope',
				solphys: 'Solar Physics',
				sovast: 'Soviet Astronomy',
				ssr: 'Space Science Reviews',
				zap: 'Zeitschrift fuer Astrophysik',
				nat: 'Nature',
				iaucirc: 'IAU Cirulars',
				aplett: 'Astrophysics Letters',
				apspr: 'Astrophysics Space Physics Research',
				bain: 'Bulletin Astronomical Institute of the Netherlands',
				fcp: 'Fundamental Cosmic Physics',
				gca: 'Geochimica Cosmochimica Acta',
				grl: 'Geophysics Research Letters',
				jcp: 'Journal of Chemical Physics',
				jgr: 'Journal of Geophysics Research',
				jqsrt: 'Journal of Quantitiative Spectroscopy and Radiative Transfer',
				memsai: 'Mem. Societa Astronomica Italiana',
				nphysa: 'Nuclear Physics A',
				physrep: 'Physics Reports',
				physscr: 'Physica Scripta',
				planss: 'Planetary Space Science',
				procspie: 'Proceedings of the SPIE',
			}[text] || text;
		},
	},
	'год': {
		selector: 'year',
	},
	'выпуск': {
		selector: 'number',
	},
	'том': {
		selector: 'volume',
	},
	'страницы': {
		selector: 'pages',
	},
	'издательство': { 
		selector: 'publisher',
	},
	'issn': {
		selector: 'issn',
	},
	'doi': {
		selector: 'doi',
	},
	'arxiv': {
		selector: 'eprint',
		map: function(text) {
			const prefix = 'arXiv:';
			if (text.indexOf(prefix) == 0)
				text = text.substr(prefix.length);
			return text;
		}
	},
	'ссылка': { const: "" },
	'язык': { const: "en" },
};

var getAuthors = function(text) {
	return text.split(' and ').map(function(name) {
		return name.replace(',', '');
	});
};

var authorsToWiki = function(authors) {
	return authors.map(function(name) {
		return new Template('nobr').add(name).toWiki() 
	}).join(', ');
};

var getWpFromObj = function(name, rules, obj) {
	var article = new Template(name);

	for(var name in rules) {
		var rule = rules[name];

		var value;
		if (rule.const !== undefined)
			value = rule.const
		else {
			if (typeof rule.selector === "function")
				value = rule.selector(obj);
			else
				value = obj[rule.selector];
			if (!value)continue;
		}

		if (rule.map)
			value = rule.map(value, obj);

		article.add(name, value);
	}

	return getRef(article);
}

var getRef = function(template) {
	for(var a of templateAdditionalArgs[template.getName()] || []) {
		template.add(a, '');
	}
	return template.toWiki();
}

var testUrl = function(regex) {
	return regex.exec(window.location.href)
}

var createWpButton = function(getResult, tag, param) {
	tag = tag || 'a';
	return $('<' + tag + '>')
		.attr('href', '#')
		.text('WP')
		.click(function(e) {
			e.preventDefault();

			var promise = $.Deferred();

			promise.done(function(result) {
				showResult(result);
			}).fail(function(result) {
				alert(result);
			});

			getResult({
				resolve: function(result) {
					promise.resolve(result)
				},
				reject: function(result) {
					promise.reject(result)
				},
			}, param);
		})
		.get(0);
}

var showResult = function(text) {
	var button;

	var dialog = $('<div>').css({
		'position': 'fixed',
		'top': 0,
		'left': 0,
		'width': '100%',
		'height': '100%',
		'z-index': 9999999,
	}).appendTo(document.body).append(
		// dimmer
		$('<div>').css({
			'width': '100%',
			'height': '100%',
			'background-color': 'black',
			'opacity': 0.6,
		})
	).append(
		// dialog container
		$('<div>').css({
			'display': 'table',
			'position': 'absolute',
			'top': 0,
			'left': 0,
			'width': '100%',
			'height': '100%',
			'font': '12px sans-serif',
		}).append(
			$('<div>').css({
				'text-align': 'center',
				'display': 'table-cell',
				'vertical-align': 'middle',
			}).append(
				// dialog
				$('<div>').css({
					'padding': 10,
					'background-color': 'white',
					'display': 'inline-block',
					'max-width': '80%',
				}).append(
					// text
					$('<div>').css({
						'padding': 5,
						'white-space': 'pre-wrap',
						'text-align': 'left',
					}).text(text)
				).append(
					// buttons
					$('<div>').css({
						'text-align': 'right',
						'margin-top': 10,
					}).append(
						button = $('<button>').text('Copy & close').click(function() {
							GM_setClipboard(text);
							dialog.remove();
						}).focus()
					).append(
						$('<button>').text('Cancel').click(function() {
							dialog.remove();
						})
					)
				)
			)
		)
	);

	button.focus();
};

var getFormData = function(data) {
	if (data && typeof(data) !== 'string') {
		var params = [];
		for(var k in data) {
			params.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k]))
		}
		data = params.join('&');
	}
	return data;
}

var ajaxGet = function(url, process) {
	var data;
	if (!(typeof url === 'string' || url instanceof String)) {
		var { url, data } = url;
	}

	if (data)
		url = url + '?' + getFormData(data);

	return ajax({
		method: 'GET',
		url: url,
	}, process);
};

var ajaxPost = function({ url, data }, process) {
	return ajax({
		method: 'POST',
		url: url,
		data: getFormData(data),
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
	}, process);
};

var ajax = function(params, process) {
	var promise = $.Deferred();

	params.onload = function(response) {
		try {
			promise.resolve(process(response.responseText));
		} catch(e) {
			promise.reject(e + '\n\n' + e.stack);
		}
	}
	setTimeout(function() {
		GM_xmlhttpRequest(params)
	}, 0);

	return promise.promise();
};

var getBibtex = function(params) {
	return function(text) {
		var bibtex = (params.parse || (t => new Bibtex(t).parse()))(text);
		var template = (params.getTemplate && params.getTemplate(bibtex)) || params.templates[bibtex.type.toLowerCase()];
		if (!template) {
			throw new Error('unknown type: ' + bibtex.type);
		}
		return getWpFromObj(template, params.getRules(bibtex.type), bibtex);
	}
};

var getXml = function(params) {
	return function(text) {
		var xml = params.parse(text);
		var template = params.getTemplate(xml);
		if (!template) {
			throw new Error('unknown type: ' + bibtex.type);
		}
		return getWpFromXml(template, params.getRules(xml), xml);
	}
};

var proecssPage = function(page, res) {
	var onButton = function(promise, param) {
		try {
			$.when(page.act(res, param)).then(promise.resolve, promise.reject);
		} catch(e) {
			promise.reject(e + '\n\n' + e.stack);
		}
	};

	page.attach(createWpButton.bind(null, onButton));
};

(function(pages) {
	try {
		for(var page of pages) {
			var res = page.test();
			if (res) {
				proecssPage(page, res);
				break;
			}
		};
	} catch(e) {
		alert(e + '\n\n' + e.stack);
	}
})([
	// pubmed
	{
		test: function() {
			return testUrl(/\/pubmed\/(\d+)$/);
		},
		attach: function(createButton) {
			$('#messagearea').after(
				$('<div>').append(createButton())
			);
		},
		act: ([,id]) => ajaxGet({
			url: id,
			data: { dopt: 'Abstract', report: 'xml', format: 'text' },
		}, getXml({
			parse: function(text) {
				var html = new DOMParser().parseFromString(text, "text/html");
				return new DOMParser().parseFromString(html.body.textContent, "text/xml");
			},
			getTemplate: () => templates.article,
			getRules: () => ({
				'автор': {
					selector: 'Article > AuthorList > Author',
					map: function(node) { return [ node.querySelector('LastName'), node.querySelector('Initials') ] },
					mapText: function(nodes) { return new Template('nobr').add(nodes[0] + ' ' + nodes[1].replace(/(.)/g, "$1. ").trim()).toWiki() },
				},
				'заглавие': {
					selector: 'Article > ArticleTitle',
					mapText: function(text) { return text.replace(/^(.*?)\.?$/, "$1") },
				},
				'издание': { 
					selector: 'Article > Journal > Title',
				},
				'год': {
					selector: 'Article > Journal > JournalIssue > PubDate',
					mapText: function(text) { return /^\s*(\d{4})/.exec(text)[1] },
				},
				'выпуск': {
					selector: 'Article > Journal > JournalIssue > Issue',
				},
				'том': {
					selector: 'Article > Journal > JournalIssue > Volume',
				},
				'страницы': {
					selector: 'Article > Pagination > MedlinePgn',
					mapText: text => text.replace('-', '—'),
				},
				'issn': {
					selector: 'Article > Journal > ISSN[IssnType=Electronic]',
				},
				'doi': {
					selector: 'ArticleIdList > ArticleId[IdType=doi]',
				},
				'pmid': {
					selector: 'ArticleIdList > ArticleId[IdType=pubmed]',
				},
				'ссылка': { const: "" },
			}),
		})),
	},
	// adsabs
	{
		test: function() {
			return testUrl(/adsabs\.harvard\.edu\/(?:abs|doi)\/(.*)$/);
		},
		attach: function(createButton) {
			$('h3').eq(0)
				.append(document.createTextNode(' '))
				.append(createButton());
		},
		act: ([,id]) => ajaxGet({
			url: '/cgi-bin/nph-bib_query',
			data: { data_type: 'BIBTEX', bibcode: id },
		}, getBibtex({
			parse: function(text) {
				var index = text.indexOf('@ARTICLE{');
				if (index == -1)
					throw new Error('bibtex not found');
				return new Bibtex(text, index).parse('article');
			},
			templates: {
				'article': templates.article,
			},
			getRules: function() {
				var bibtex = clone(bibtexBase);
				bibtex.bibcode = { selector: 'bibcode' };
				return bibtex;
			},
		})),
	},
	// ufn
	{
		test: function() {
			return testUrl(/\/ufn\.ru\/ru\/articles\/(\d+\/\d+\/\w+)\//);
		},
		attach: function(createButton) {
			$('#print > table tr > td').next().append(
				$('<td>').append(createButton())
			);
		},
		act: ([, id]) => ajaxGet('/ru/articles/' + id + '/citation/ru/bibtex.html', getBibtex({
			parse: function(text) {
				var html = new DOMParser().parseFromString(text, "text/html");
				var node = html.body.querySelector('.cit_code > pre');
				if (!node)
					throw new Error('bibtex not found');
				return new Bibtex(node.textContent).parse('article');
			},
			templates: {
				'article': templates.article,
			},
			getRules: function() {
				var bibtex = clone(bibtexBase);

				var nameRegex = /^(.+) (\S+)$/;
				bibtex.автор.map = function(text) {
					return authorsToWiki(getAuthors(text).map(function(name) {
						var match = nameRegex.exec(name);
						if (!match) return name;
						return match[2] + ' ' + match[1];
					}));
				}
				bibtex.страницы.map = t => t.replace('-', '—');
				bibtex.ссылка = { const: 'http://ufn.ru/ru/articles/' + id + '/' };
				bibtex.язык = { const: "ru" };

				return bibtex;
			}
		})),
	},
	// google books
	{
		test: function() {
			return window.self == window.top && testUrl(/https?:\/\/books\.google\..+\/books.*[?&]id=([^&$]+)/);
		},
		attach: function(createButton) {
			var button = $(createButton()).addClass('gb-button').css('margin-left', '4px');
			$('.metadata_value > .gb-button:last-child, #gb-get-book-container > *:first-child')
			 	.parent()
			 	.append(button);
		},
		act: ([, id]) => ajaxGet({
			url: 'http://books.google.us/books/download/',
			data: { id: id, output: 'bibtex' },
		}, getBibtex({
			parse: t => new Bibtex(t).parse('book'),
			templates: {
				'book': templates.book
			},
			getRules: function() {
				var bibtex = clone(bibtexBase);
				bibtex.серия = { 'selector': 'series' };
				bibtex.страниц = { const: "" };
				bibtex.isbn = { 'selector': 'isbn' };
				bibtex.ссылка = { const: 'http://books.google.com/books?id=' + id };
				return bibtex;
			}
		})),
	},
	// sciencedirect
	{
		test: function() {
			return testUrl(/sciencedirect\.com\/science\/article\//);
		},
		attach: function(createButton) {
			$('#articleNav > ul').prepend($('<li />').append(
				$(createButton()).attr('type', 'button').val('WP').css({
					'vertical-align': 'middle',
					'line-height': '40px',
				})
			));
		},
		act: function() {
			var params = {
				'citation-type': 'BIBTEX'
			};
			$('form[name=exportCite] input[type=hidden]').each(function(i, hidden) {
				params[$(hidden).attr('name')] = $(hidden).val();
			});
			return ajaxPost({
				url: $('form[name=exportCite]').attr('action'),
				data: params,
			}, getBibtex({
				parse: t => new Bibtex(t).parse('article'),
				templates: {
					'article': templates.article
				},
				getRules: function() {
					var bibtex = clone(bibtexBase);
					bibtex.ссылка = { selector: 'url' };
					bibtex.doi.map = function(text) {
						var res = /http:\/\/dx\.doi\.org\/(.*)$/.exec(text) || [];
						return res[1] || text;
					}
					bibtex.страницы.map = function(text) {
						return text.replace(' - ', '—')
					}
					return bibtex;
				}
			}))
		},
	},
	// Library Genesis
	{
		test: function() {
			return testUrl(/gen\.lib\.rus\.ec\/scimag\//);
		},
		attach: function(createButton) {
			var node = document.querySelector('form[name=search]');
			while(node && node.nodeName != 'TABLE') {
				node = node.nextSibling;
			}
			var rows = node.querySelectorAll('tr');
			for(var i = 1; i < rows.length; i++) {
				let anchor = rows[i].querySelector('td:first-child > b');
				if (!anchor) 
					continue;
				var button = createButton('a', anchor.textContent);
				rows[i].querySelector('td:last-child').appendChild(button);
				button.style.fontWeight = 'bold';
			}
		},
		act: (_, doi) => ajaxGet({
			url: 'http://gen.lib.rus.ec/scimag/bibtex.php',
			data: { doi: doi },
		}, getBibtex({
			parse: text => {
				var html = new DOMParser().parseFromString(text, "text/html");
				return new Bibtex(html.querySelector('textarea#bibtext').value).parse('article');
			},
			templates: {
				'article': templates.article
			},
			getRules: function() {
				var bibtex = clone(bibtexBase);
				bibtex.выпуск = { selector: 'issue' };
				bibtex.страницы = { selector: 'page' };
				bibtex.язык = { const: '' };
				return bibtex;
			},
		})),
	},
	// wiley
	{
		test: function() {
			return testUrl(/onlinelibrary\.wiley\.com\/doi\//)
		},
		attach: function(createButton) {
			$('#promosAndTools .titleTools').append($('<li>').append(createButton()));
		},
		act: function() {
			var [, doi] = /^DOI:\s+(.+)$/.exec($('#doi').text().trim());
			if (!doi) throw new Error('doi not found');

			return ajaxPost({
				url: '/documentcitationdownloadformsubmit',
				data: { doi: doi, fileFormat: 'BIBTEX', hasAbstract: 'CITATION' },
			}, getBibtex({
				templates: {
					'article': templates.article,
				},
				getRules: function() {
					var bibtex = clone(bibtexBase);
					bibtex.ссылка = { selector: 'url' };
					return bibtex;
				}
			}))
		},
	},
	// jstor
	{
		test: function() {
			return testUrl(/www\.jstor\.org\/[^\/]+\/(.*?)($|\?)/)
		},
		attach: function(createButton) {
			$('#citation-tools-drop').append($('<li>').append(createButton()));
		},
		act: () => ajaxPost({
			url: '/action/downloadSingleCitationSec?userAction=export&format=bibtex&include=abs&singleCitation=true',
			data: { noDoi: 'yesDoi', doi: /DOI:\s*(.*)/.exec($('.doi').text().trim())[1] },
		}, getBibtex({
			parse: txt => {
				txt = txt.trim();
				const header = 'JSTOR CITATION LIST';

				var index = txt.indexOf(header);
				if (index == -1)
					throw new Error('header not found');
				index += header.length;

				while(index < txt.length) {
					var bibtex = new Bibtex(txt, index);
					var result = bibtex.parse([ 'article', 'comment', 'book' ]);
					if (result.type != 'comment')
						return result;
					index = bibtex.index();
				}
				throw new Error('bibtex not found');
			},
			templates: {
				'article': templates.article,
				'book': templates.book,
			},
			getRules: function() {
				var bibtex = clone(bibtexBase);
				bibtex.ссылка = { selector: 'url' };
				bibtex.выпуск.selector = function(obj) {
					return obj['number'] || obj['jstor_issuetitle'];
				}
				bibtex.страницы.map = function(text) {
					return text.replace(/^p?p\. /, "").replace('-', '—');
				};
				return bibtex;
			},
		})),
	},
	// projecteuclid
	{
		test: function() {
			return testUrl(/projecteuclid.org\/(euclid\..*?\/\d+)/);
		},
		attach: function(createButton) {
			$('#export-form').append($(createButton()).addClass('btn export-link-special'))
		},
		act: ([, id]) => ajaxPost({
			url: '/export_citations',
			data: { format: "bibtex", delivery: "browser", address: '', h: id },
		}, getBibtex({
			templates: { 
				'article': templates.article,
				'inbook': templates.book,
			},
			getRules: function(type) {
				var bibtex = clone(bibtexBase);
				bibtex.издание = { selector: 'fjournal' };

				if (type.toLowerCase() == 'inbook') {
					bibtex.место = { selector: 'address' };
					bibtex.часть = bibtex.заглавие;
					bibtex.заглавие = { selector: 'booktitle' };
					bibtex['ссылка часть'] = { selector: 'url' };
					delete bibtex.ссылка;
				} else {
					bibtex.ссылка = { selector: 'url' };
				}

				return bibtex;
			}
		})),
	},
	// springer
	{
		test: () => testUrl(/\/link\.springer\.com\/([^#]+)/),
		attach: function(createButton) {
			$('.other-actions > ul').append($('<li>').append(createButton()));
		},
		act: ([, id]) => ajaxGet('http://link.springer.com/export-citation/' + id + '.bib', getBibtex({
			parse: text => new Bibtex(text.replace(/^@.*?{/, '$&x,')).parse(),
			templates: {
				'article': templates.article,
				'incollection': templates.book,
			},
			getRules: function(type) {
				var bibtex = clone(bibtexBase);
				bibtex.страницы.map = t => t.replace('-', '—');
				bibtex.ссылка = { const: 'http://link.springer.com/' + id };
				if (type == 'incollection') {
					bibtex.ответственный = { 
						selector: 'editor',
						map: text => 'Ed. by ' + authorsToWiki(getAuthors(text)),
					};
					bibtex.серия = { 'selector': 'series' };
				} else if (type == 'article') {
					delete bibtex.издательство;
				}
				return bibtex;
			}
		})),
	},
	// mathnet
	{
		test: () => testUrl(/\/www\.mathnet\.ru\//),
		attach: function(createButton) {
			$('#citPaperAMSBIBID').before(createButton());
		},
		act: () => getBibtex({
			parse: t => {
				var transl = false;
				var res = {};
				for(var line of t.split('\n')) {
					if (!line) continue;
					var [, key, value] = /^\\(\w+)\s*(.*)$/.exec(line);
					if (key === 'transl' || key === 'rtransl') {
						transl = true;
						continue;
					}
					if (transl)
						key = 'transl_' + key;
					if (key == 'by') 
						value = value.replace(/([^\\]), /g, '$1 and ');
					res[key] = new Bibtex(value).parseAmsFieldValue();
				}
				return res;
			},
			getTemplate: () => templates.article,
			getRules: () => ({
				'автор': { 
					selector: 'by',
					map: text => authorsToWiki(getAuthors(text)),
				},
				'заглавие': { selector: 'paper' },
				'издание': { selector: b => b.journalname || b.jour },
				'год': { selector: 'yr' },
				'выпуск': { selector: 'issue' },
				'том': { selector: 'vol' },
				'страницы': { selector: 'pages' },
				'издательство': { selector: 'publ' },
				'ссылка': { selector: 'mathnet' },
				'язык': { const: 'ru' },
			}),
		})($('#citPaperAMSBIBID pre').text())
	},
]);