
Files are copied from translators.zip and styles.zip (or, for SVN installs, 'translators' and (for now) 'csl' directories) in the installation directory to 'translators' and 'styles' directories in the data directory. A build_zip file is provided for testing translators.zip (which will take precedence over a 'translators' directory) but isn't required. The timestamp stored in repotime.txt is stored in the database and is sent to the server for updates since that time. Updating a file in [install-dir]/translators or [install-dir]/styles automatically copies all files in that directory to the data directory.
3276 lines
96 KiB
JavaScript
3276 lines
96 KiB
JavaScript
/*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (c) 2006 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://chnm.gmu.edu
|
|
|
|
Licensed under the Educational Community License, Version 1.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.opensource.org/licenses/ecl1.php
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
/**
|
|
* @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded
|
|
* every time a translator is accessed
|
|
* @property {Zotero.CSL} lastCSL
|
|
*/
|
|
Zotero.Styles = new function() {
|
|
var _initialized = false;
|
|
var _styles, _visibleStyles;
|
|
|
|
this.ios = Components.classes["@mozilla.org/network/io-service;1"].
|
|
getService(Components.interfaces.nsIIOService);
|
|
|
|
/**
|
|
* Initializes styles cache, loading metadata for styles into memory
|
|
*/
|
|
this.init = function() {
|
|
_initialized = true;
|
|
|
|
var start = (new Date()).getTime()
|
|
|
|
_styles = {};
|
|
_visibleStyles = [];
|
|
this.cacheTranslatorData = Zotero.Prefs.get("cacheTranslatorData");
|
|
this.lastCSL = null;
|
|
|
|
// main dir
|
|
var dir = Zotero.getStylesDirectory();
|
|
var i = _readStylesFromDirectory(dir, false);
|
|
|
|
// hidden dir
|
|
dir.append("hidden");
|
|
if(dir.exists()) i += _readStylesFromDirectory(dir, true);
|
|
|
|
Zotero.debug("Cached "+i+" styles in "+((new Date()).getTime() - start)+" ms");
|
|
}
|
|
|
|
/**
|
|
* Reads all styles from a given directory and caches their metadata
|
|
*/
|
|
function _readStylesFromDirectory(dir, hidden) {
|
|
var i = 0;
|
|
var contents = dir.directoryEntries;
|
|
while(contents.hasMoreElements()) {
|
|
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile);
|
|
if(!file.leafName || file.leafName[0] == "." || file.isDirectory()) continue;
|
|
|
|
var style = new Zotero.Style(file);
|
|
if(style.styleID) {
|
|
if(_styles[style.styleID]) {
|
|
// same style is already cached
|
|
Zotero.log('Style with ID '+style.styleID+' already loaded from "'+
|
|
_styles[style.styleID].file.leafName+'"', "error",
|
|
Zotero.Styles.ios.newFileURI(style.file).spec);
|
|
} else {
|
|
// add to cache
|
|
_styles[style.styleID] = style;
|
|
_styles[style.styleID].hidden = hidden;
|
|
if(!hidden) _visibleStyles.push(style);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Gets a style with a given ID
|
|
* @param {String} id
|
|
*/
|
|
this.get = function(id) {
|
|
if(!_initialized) this.init();
|
|
return _styles[id] ? _styles[id] : false;
|
|
}
|
|
|
|
/**
|
|
* Gets all visible styles
|
|
*/
|
|
this.getVisible = function() {
|
|
if(!_initialized || !this.cacheTranslatorData) this.init();
|
|
return _visibleStyles.slice(0);
|
|
}
|
|
|
|
/**
|
|
* Gets all styles
|
|
*/
|
|
this.getAll = function() {
|
|
if(!_initialized || !this.cacheTranslatorData) this.init();
|
|
return _styles;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @class Represents a style file and its metadata
|
|
* @property {String} styleID
|
|
* @property {String} type "csl" for CSL styles, "ens" for legacy styles
|
|
* @property {String} title
|
|
* @property {String} updated SQL-style date updated
|
|
* @property {String} class "in-text" or "note"
|
|
* @property {String} source The CSL that contains the formatting information for this one, or null
|
|
* if this CSL contains formatting information
|
|
* @property {Zotero.CSL} csl The Zotero.CSL object used to format using this style
|
|
* @property {Boolean} hidden True if this style is hidden in style selection dialogs, false if it
|
|
* is not
|
|
*/
|
|
Zotero.Style = function(file) {
|
|
this.file = file;
|
|
|
|
var extension = file.leafName.substr(-4).toLowerCase();
|
|
if(extension == ".ens") {
|
|
this.type = "ens";
|
|
|
|
this.styleID = Zotero.Styles.ios.newFileURI(this.file).spec;
|
|
this.title = file.leafName.substr(0, file.leafName.length-4);
|
|
this.updated = Zotero.Date.dateToSQL(new Date(file.lastModifiedTime));
|
|
} else if(extension == ".csl") {
|
|
// "with ({});" needed to fix default namespace scope issue
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
|
|
|
this.type = "csl";
|
|
|
|
var xml = Zotero.CSL.Global.cleanXML(Zotero.File.getContents(file));
|
|
try {
|
|
xml = new XML(xml);
|
|
} catch(e) {
|
|
Zotero.log(e.toString(), "error",
|
|
Zotero.Styles.ios.newFileURI(this.file).spec, xml.split(/\r?\n/)[e.lineNumber-1],
|
|
e.lineNumber);
|
|
return;
|
|
}
|
|
|
|
this.styleID = xml.info.id.toString();
|
|
this.title = xml.info.title.toString();
|
|
this.updated = xml.info.updated.toString().replace(/(.+)T([^\+]+)\+?.*/, "$1 $2");
|
|
this._class = xml.@class.toString();
|
|
|
|
this.source = null;
|
|
for each(var link in xml.info.link) {
|
|
if(link.@rel == "source") {
|
|
this.source = link.@href.toString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.Style.prototype.__defineGetter__("csl",
|
|
/**
|
|
* Retrieves the Zotero.CSL object for this style
|
|
* @type Zotero.CSL
|
|
*/
|
|
function() {
|
|
// cache last style
|
|
if(Zotero.Styles.cacheTranslatorData && Zotero.Styles.lastCSL.styleID == this.styleID) {
|
|
return Zotero.Styles.lastCSL;
|
|
}
|
|
|
|
if(this.type == "ens") {
|
|
// EN style
|
|
var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIFileInputStream);
|
|
iStream.init(this.file, 0x01, 0664, 0);
|
|
var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
|
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
|
bStream.setInputStream(iStream);
|
|
var string = bStream.readBytes(this.file.fileSize);
|
|
iStream.close();
|
|
|
|
var enConverter = new Zotero.ENConverter(string, null, this.title);
|
|
var xml = enConverter.parse();
|
|
} else {
|
|
// "with ({});" needed to fix default namespace scope issue
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
|
|
|
if(this.source) {
|
|
// parent/child
|
|
var formatCSL = Zotero.Styles.get(this.source);
|
|
if(!formatCSL) {
|
|
throw(new Error('Style references '+this.source+', but this style is not installed',
|
|
Zotero.Styles.ios.newFileURI(this.file).spec, null));
|
|
}
|
|
var file = formatCSL.file;
|
|
} else {
|
|
var file = this.file;
|
|
}
|
|
|
|
var cslString = Zotero.File.getContents(file);
|
|
var xml = new XML(Zotero.CSL.Global.cleanXML(cslString));
|
|
}
|
|
|
|
return (Zotero.Styles.lastCSL = new Zotero.CSL(xml));
|
|
});
|
|
|
|
Zotero.Style.prototype.__defineGetter__("class",
|
|
/**
|
|
* Retrieves the style class, either from the metadata that's already loaded or by loading the file
|
|
* @type String
|
|
*/
|
|
function() {
|
|
if(this._class) return this._class;
|
|
return (this._class = this.csl.class);
|
|
});
|
|
|
|
/**
|
|
* Deletes a style
|
|
*/
|
|
Zotero.Style.prototype.delete = function() {
|
|
this.file.remove(false);
|
|
Zotero.Styles.init();
|
|
}
|
|
|
|
|
|
Zotero.Styles.MIMEHandler = new function () {
|
|
this.init = init;
|
|
|
|
/*
|
|
* registers URIContentListener to handle MIME types
|
|
*/
|
|
function init() {
|
|
Zotero.debug("Registering URIContentListener for text/x-csl");
|
|
var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
|
|
.getService(Components.interfaces.nsIURILoader);
|
|
uriLoader.registerContentListener(Zotero.Styles.MIMEHandler.URIContentListener);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Zotero.Styles.MIMEHandler.URIContentListener: implements
|
|
* nsIURIContentListener interface to grab MIME types
|
|
*/
|
|
Zotero.Styles.MIMEHandler.URIContentListener = new function() {
|
|
// list of content types to capture
|
|
// NOTE: must be from shortest to longest length
|
|
this.desiredContentTypes = ["text/x-csl"];
|
|
|
|
this.QueryInterface = QueryInterface;
|
|
this.canHandleContent = canHandleContent;
|
|
this.doContent = doContent;
|
|
this.isPreferred = isPreferred;
|
|
this.onStartURIOpen = onStartURIOpen;
|
|
|
|
function QueryInterface(iid) {
|
|
if (iid.equals(Components.interfaces.nsISupports)
|
|
|| iid.equals(Components.interfaces.nsISupportsWeakReference)
|
|
|| iid.equals(Components.interfaces.nsIURIContentListener)) {
|
|
return this;
|
|
}
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
function canHandleContent(contentType, isContentPreferred, desiredContentType) {
|
|
if (this.desiredContentTypes.indexOf(contentType) != -1) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function doContent(contentType, isContentPreferred, request, contentHandler) {
|
|
Zotero.debug("Running doContent() for " + request.name);
|
|
contentHandler.value = new Zotero.Styles.MIMEHandler.StreamListener(request, contentType);
|
|
return false;
|
|
}
|
|
|
|
function isPreferred(contentType, desiredContentType) {
|
|
if (this.desiredContentTypes.indexOf(contentType) != -1) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function onStartURIOpen(URI) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Zotero.Styles.MIMEHandler.StreamListener: implements nsIStreamListener and
|
|
* nsIRequestObserver interfaces to download MIME types we've grabbed
|
|
*/
|
|
Zotero.Styles.MIMEHandler.StreamListener = function(request, contentType) {
|
|
this._request = request;
|
|
this._contentType = contentType
|
|
this._readString = "";
|
|
this._scriptableStream = null;
|
|
this._scriptableStreamInput = null
|
|
|
|
Zotero.debug("Prepared to grab content type " + contentType);
|
|
}
|
|
|
|
Zotero.Styles.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) {
|
|
if (iid.equals(Components.interfaces.nsISupports)
|
|
|| iid.equals(Components.interfaces.nsIRequestObserver)
|
|
|| iid.equals(Components.interfaces.nsIStreamListener)) {
|
|
return this;
|
|
}
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
Zotero.Styles.MIMEHandler.StreamListener.prototype.onStartRequest = function(channel, context) {}
|
|
|
|
/*
|
|
* Called when there's data available; basically, we just want to collect this data
|
|
*/
|
|
Zotero.Styles.MIMEHandler.StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) {
|
|
Zotero.debug(count + " bytes available");
|
|
|
|
if (inputStream != this._scriptableStreamInput) {
|
|
this._scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
|
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
|
this._scriptableStream.init(inputStream);
|
|
this._scriptableStreamInput = inputStream;
|
|
}
|
|
this._readString += this._scriptableStream.read(count);
|
|
}
|
|
|
|
/*
|
|
* Called when the request is done
|
|
*/
|
|
Zotero.Styles.MIMEHandler.StreamListener.prototype.onStopRequest = function(channel, context, status) {
|
|
Zotero.debug("Request finished");
|
|
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
|
|
.getService(Components.interfaces.nsIExternalHelperAppService);
|
|
|
|
if (this._request.name) {
|
|
var loadURI = this._request.name;
|
|
}
|
|
else {
|
|
var loadURI = '';
|
|
}
|
|
|
|
Zotero.Styles.install(this._readString, loadURI);
|
|
}
|
|
|
|
|
|
/*
|
|
* CSL: a class for creating bibliographies from CSL files
|
|
* this is abstracted as a separate class for the benefit of anyone who doesn't
|
|
* want to use the Scholar data model, but does want to use CSL in JavaScript
|
|
*/
|
|
Zotero.CSL = function(csl) {
|
|
// "with ({});" needed to fix default namespace scope issue
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
|
|
|
if(typeof csl != "XML") {
|
|
this._csl = new XML(Zotero.CSL.Global.cleanXML(csl));
|
|
} else {
|
|
this._csl = CSL;
|
|
}
|
|
|
|
// initialize CSL
|
|
Zotero.CSL.Global.init();
|
|
|
|
// load localizations
|
|
this._terms = Zotero.CSL.Global.parseLocales(this._csl.terms);
|
|
|
|
// load class and styleID
|
|
this.styleID = this._csl.info.id.toString();
|
|
this.class = this._csl["@class"].toString();
|
|
Zotero.debug("CSL: style class is "+this.class);
|
|
|
|
this.hasBibliography = (this._csl.bibliography.length() ? 1 : 0);
|
|
}
|
|
|
|
/*
|
|
* Constants for citation positions
|
|
*/
|
|
Zotero.CSL.POSITION_FIRST = 0;
|
|
Zotero.CSL.POSITION_SUBSEQUENT = 1;
|
|
Zotero.CSL.POSITION_IBID = 2;
|
|
Zotero.CSL.POSITION_IBID_WITH_LOCATOR = 3;
|
|
|
|
|
|
Zotero.CSL._dateVariables = {
|
|
"issued":true,
|
|
"accessDate":true
|
|
}
|
|
|
|
Zotero.CSL._namesVariables = {
|
|
"editor":true,
|
|
"translator":true,
|
|
"recipient":true,
|
|
"interviewer":true,
|
|
"collection-editor":true,
|
|
"author":true
|
|
}
|
|
|
|
/*
|
|
* Constants for name (used for disambiguate-add-givenname)
|
|
*/
|
|
Zotero.CSL.NAME_USE_INITIAL = 1;
|
|
Zotero.CSL.NAME_USE_FULL = 2;
|
|
|
|
/*
|
|
* generate an item set
|
|
*/
|
|
Zotero.CSL.prototype.createItemSet = function(items) {
|
|
return new Zotero.CSL.ItemSet(items, this);
|
|
}
|
|
|
|
/*
|
|
* generate a citation object
|
|
*/
|
|
Zotero.CSL.prototype.createCitation = function(citationItems) {
|
|
return new Zotero.CSL.Citation(citationItems, this);
|
|
}
|
|
|
|
/*
|
|
* create a citation (in-text or footnote)
|
|
*/
|
|
Zotero.CSL._firstNameRegexp = /^[^\s]*/;
|
|
Zotero.CSL._textCharRegexp = /[a-zA-Z0-9]/;
|
|
Zotero.CSL._numberRegexp = /\d+/;
|
|
Zotero.CSL.prototype.formatCitation = function(citation, format) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
var context = this._csl.citation;
|
|
if(!context) {
|
|
throw "CSL: formatCitation called on style with no citation context";
|
|
}
|
|
if(!citation.citationItems.length) {
|
|
throw "CSL: formatCitation called with empty citation";
|
|
}
|
|
|
|
// clone citationItems, so as not to disturb the citation
|
|
var citationItems = citation.citationItems;
|
|
|
|
// handle collapse
|
|
var cslAdded = [];
|
|
|
|
var collapse = context.option.(@name == "collapse").@value.toString();
|
|
if(collapse) {
|
|
// clone citationItems, so as not to disturb the citation
|
|
citationItems = new Array();
|
|
|
|
if(collapse == "citation-number") {
|
|
// loop through, collecting citation numbers
|
|
var citationNumbers = new Object();
|
|
for(var i=0; i<citation.citationItems.length; i++) {
|
|
citationNumbers[citation.citationItems[i].item.getProperty("citation-number")] = i;
|
|
}
|
|
// add -1 at the end so that the last span gets added (loop below
|
|
// must be run once more)
|
|
citationNumbers[-1] = false;
|
|
|
|
var previousI = -1;
|
|
var span = [];
|
|
// loop through citation numbers and collect ranges in span
|
|
for(var i in citationNumbers) {
|
|
if(i != -1 && !citation.citationItems[citationNumbers[i]].prefix
|
|
&& !citation.citationItems[citationNumbers[i]].suffix
|
|
&& i == parseInt(previousI, 10)+1) {
|
|
// could be part of a range including the previous number
|
|
span.push(citationNumbers[i]);
|
|
} else { // not part of a range
|
|
if(span.length) citationItems[span[0]] = citation.citationItems[span[0]];
|
|
if(span.length > 2) {
|
|
// if previous set of citations was a range, collapse them
|
|
var firstNumber = citationItems[span[0]].item.getProperty("citation-number");
|
|
citationItems[span[0]]._csl = {"citation-number":(firstNumber+"-"+(parseInt(firstNumber, 10)+span.length-1))};
|
|
cslAdded.push(span[0]);
|
|
} else if(span.length == 2) {
|
|
citationItems[span[1]] = citation.citationItems[span[1]];
|
|
}
|
|
|
|
span = [citationNumbers[i]];
|
|
}
|
|
previousI = i;
|
|
}
|
|
} else if(collapse.substr(0, 4) == "year") {
|
|
// loop through, collecting citations (sans date) in an array
|
|
var lastNames = {};
|
|
for(var i=0; i<citation.citationItems.length; i++) {
|
|
var citationString = new Zotero.CSL.FormattedString(context, format);
|
|
this._processElements(citation.citationItems[i].item, context.layout, citationString,
|
|
context, null, [{"issued":true}, {}]);
|
|
var cite = citationString.get();
|
|
|
|
// put into lastNames array
|
|
if(!lastNames[cite]) {
|
|
lastNames[cite] = [i];
|
|
} else {
|
|
lastNames[cite].push(i);
|
|
}
|
|
}
|
|
|
|
for(var i in lastNames) {
|
|
var itemsSharingName = lastNames[i];
|
|
if(itemsSharingName.length == 1) {
|
|
// if only one, don't worry about grouping
|
|
citationItems[itemsSharingName[0]] = citation.citationItems[itemsSharingName[0]];
|
|
} else {
|
|
var years = [];
|
|
// if grouping by year-suffix, we need to do more (to pull
|
|
// together various letters)
|
|
if(collapse == "year-suffix" && context.option.(@name == "disambiguate-add-year-suffix").@value == "true") {
|
|
var yearsArray = new Object();
|
|
for(var j=0; j<itemsSharingName.length; j++) {
|
|
var year = citation.citationItems[itemsSharingName[j]].item.getDate("issued");
|
|
if(year) {
|
|
year = year.getDateVariable("year");
|
|
if(year) {
|
|
// add to years
|
|
if(!yearsArray[year]) {
|
|
yearsArray[year] = [itemsSharingName[j]];
|
|
} else {
|
|
yearsArray[year].push(itemsSharingName[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!year) {
|
|
// if no year, just copy
|
|
years.push("");
|
|
}
|
|
}
|
|
|
|
// loop through all years
|
|
for(var j in yearsArray) {
|
|
var citationItem = citation.citationItems[yearsArray[j][0]];
|
|
|
|
// push first year with any suffix
|
|
var year = j;
|
|
var suffix = citationItem.item.getProperty("disambiguate-add-year-suffix");
|
|
if(suffix) year += suffix;
|
|
years.push(year);
|
|
|
|
// also push subsequent years
|
|
if(yearsArray[j].length > 1) {
|
|
for(k=1; k<yearsArray[j].length; k++) {
|
|
var suffix = citation.citationItems[yearsArray[j][k]].item.getProperty("disambiguate-add-year-suffix");
|
|
if(suffix) years.push(suffix);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// just add years
|
|
for(var j=0; j<itemsSharingName.length; j++) {
|
|
var item = citation.citationItems[itemsSharingName[j]].item;
|
|
var year = item.getDate("issued");
|
|
if(year) {
|
|
years[j] = year.getDateVariable("year");
|
|
var suffix = item.getProperty("disambiguate-add-year-suffix");
|
|
if(suffix) years[j] += suffix;
|
|
}
|
|
}
|
|
}
|
|
citation.citationItems[itemsSharingName[0]]._csl = {"issued":{"year":years.join(", ")}};
|
|
citationItems[itemsSharingName[0]] = citation.citationItems[itemsSharingName[0]];
|
|
cslAdded.push(itemsSharingName[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var string = new Zotero.CSL.FormattedString(context, format, context.layout.@delimiter.toString());
|
|
for(var i=0; i<citationItems.length; i++) {
|
|
var citationItem = citationItems[i];
|
|
if(!citationItem) continue;
|
|
|
|
var citationString = string.clone();
|
|
|
|
// suppress author if requested
|
|
var ignore = citationItem.suppressAuthor ? [{"author":true}, {}] : undefined;
|
|
|
|
// add prefix
|
|
if(citationItem.prefix) {
|
|
var prefix = citationItem.prefix;
|
|
|
|
// add space to prefix if last char is alphanumeric
|
|
if(Zotero.CSL._textCharRegexp.test(prefix[prefix.length-1])) prefix += " ";
|
|
|
|
citationString.append(prefix);
|
|
}
|
|
|
|
this._processElements(citationItem.item, context.layout, citationString,
|
|
context, citationItem, ignore);
|
|
|
|
// add suffix
|
|
if(citationItem.suffix) {
|
|
var suffix = citationItem.suffix;
|
|
|
|
// add space to suffix if last char is alphanumeric
|
|
if(Zotero.CSL._textCharRegexp.test(suffix[0])) suffix = " "+suffix;
|
|
|
|
citationString.append(suffix);
|
|
}
|
|
|
|
string.concat(citationString);
|
|
}
|
|
|
|
var returnString = string.clone();
|
|
returnString.concat(string, context.layout);
|
|
var returnString = returnString.get();
|
|
|
|
// loop through to remove _csl property
|
|
for(var i=0; i<cslAdded.length; i++) {
|
|
citationItems[cslAdded[i]]._csl = undefined;
|
|
}
|
|
|
|
return returnString;
|
|
}
|
|
|
|
/*
|
|
* create a bibliography
|
|
*/
|
|
Zotero.CSL.prototype.formatBibliography = function(itemSet, format) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
var context = this._csl.bibliography;
|
|
if(!context.length()) {
|
|
context = this._csl.citation;
|
|
var isCitation = true;
|
|
}
|
|
if(!context) {
|
|
throw "CSL: formatBibliography called on style with no bibliography context";
|
|
}
|
|
|
|
if(!itemSet.items.length) return "";
|
|
|
|
var hangingIndent = !!(context.option.(@name == "hanging-indent").@value == "true");
|
|
var secondFieldAlign = context.option.(@name == "second-field-align").@value.toString();
|
|
var lineSpacing = context.option.(@name == "line-spacing").@value.toString();
|
|
lineSpacing = (lineSpacing === "" ? 1 : parseInt(lineSpacing, 10));
|
|
if(lineSpacing == NaN) throw "Invalid line spacing";
|
|
var entrySpacing = context.option.(@name == "entry-spacing").@value.toString();
|
|
entrySpacing = (entrySpacing === "" ? 1 : parseInt(entrySpacing, 10));
|
|
if(entrySpacing == NaN) throw "Invalid entry spacing";
|
|
|
|
var index = 0;
|
|
var output = "";
|
|
var preamble = "";
|
|
if(format == "HTML") {
|
|
if(this.class == "note" && isCitation) {
|
|
// note citations are formatted as an ordered list
|
|
preamble = '<ol>\r\n';
|
|
secondFieldAlign = false;
|
|
} else {
|
|
// needed bc HTML doesn't force lines to be at least as big as the
|
|
// tallest character
|
|
if(lineSpacing <= 1.1) lineSpacing = 1.1;
|
|
|
|
// add style
|
|
var style = 'line-height:'+lineSpacing+'em;'
|
|
if(hangingIndent) {
|
|
style += 'margin-left:0.5in;text-indent:-0.5in;';
|
|
}
|
|
|
|
if(secondFieldAlign) {
|
|
preamble += '<table style="border-collapse:collapse;'+style+'">\r\n';
|
|
} else {
|
|
preamble += '<div style="'+style+'">\r\n';
|
|
}
|
|
}
|
|
} else {
|
|
if(format == "RTF" || format == "Integration") {
|
|
if(format == "RTF") {
|
|
preamble = "{\\rtf\\ansi{\\fonttbl\\f0\\froman Times New Roman;}{\\colortbl;\\red255\\green255\\blue255;}\\pard\\f0\r\n";
|
|
}
|
|
|
|
var tabStop = null;
|
|
if(hangingIndent) {
|
|
var indent = 720; // 720 twips = 0.5 in
|
|
var firstLineIndent = -720; // -720 twips = -0.5 in
|
|
} else {
|
|
var indent = 0;
|
|
var firstLineIndent = 0;
|
|
}
|
|
}
|
|
|
|
var returnChars = "";
|
|
for(j=0; j<=entrySpacing; j++) {
|
|
if(format == "RTF") {
|
|
returnChars += "\\\r\n";
|
|
} else if(Zotero.isWin) {
|
|
returnChars += "\r\n";
|
|
} else {
|
|
returnChars += "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
var maxFirstFieldLength = 0;
|
|
for(var i in itemSet.items) {
|
|
var item = itemSet.items[i];
|
|
if(item == undefined) continue;
|
|
|
|
// try to get custom bibliography
|
|
var string = item.getProperty("bibliography-"+(format == "Integration" ? "RTF" : format));
|
|
if(!string) {
|
|
string = new Zotero.CSL.FormattedString(context, format);
|
|
this._processElements(item, context.layout, string, context);
|
|
if(!string) {
|
|
continue;
|
|
}
|
|
|
|
// add format
|
|
string.string = context.layout.@prefix.toString() + string.string;
|
|
if(context.layout.@suffix.length()) {
|
|
string.append(context.layout.@suffix.toString());
|
|
}
|
|
|
|
string = string.get();
|
|
}
|
|
|
|
if(secondFieldAlign && (format == "RTF" || format == "Integration")) {
|
|
if(format == "RTF") {
|
|
var tab = string.indexOf("\\tab ");
|
|
} else {
|
|
var tab = string.indexOf("\t");
|
|
}
|
|
if(tab > maxFirstFieldLength) {
|
|
maxFirstFieldLength = tab;
|
|
}
|
|
}
|
|
|
|
// add line feeds
|
|
if(format == "HTML") {
|
|
var coins = Zotero.OpenURL.createContextObject(item.zoteroItem, "1.0");
|
|
|
|
var span = (coins ? ' <span class="Z3988" title="'+coins.replace("&", "&", "g")+'"> </span>' : '');
|
|
|
|
if(this.class == "note" && isCitation) {
|
|
output += "<li>"+string+span+"</li>\r\n";
|
|
} else if(secondFieldAlign) {
|
|
output += '<tr style="vertical-align:top;"><td>'+string+span+"</td></tr>\r\n";
|
|
for(var j=0; j<entrySpacing; j++) {
|
|
output += '<tr><td colspan="2"> </td></tr>\r\n';
|
|
}
|
|
} else {
|
|
if(i == 0) {
|
|
// first p has no margins
|
|
var margin = "0";
|
|
} else {
|
|
var margin = (entrySpacing*lineSpacing).toString()+"em 0 0 0";
|
|
}
|
|
output += '<p style="margin:'+margin+'">'+string+span+"</p>\r\n";
|
|
}
|
|
} else {
|
|
if(this.class == "note" && isCitation) {
|
|
if(format == "RTF") {
|
|
index++;
|
|
output += index+". ";
|
|
} else if(format == "Text") {
|
|
index++;
|
|
output += index+". ";
|
|
}
|
|
}
|
|
output += string+returnChars;
|
|
}
|
|
}
|
|
|
|
if(format == "HTML") {
|
|
if(this.class == "note" && isCitation) {
|
|
output += '</ol>';
|
|
} else if(secondFieldAlign) {
|
|
output += '</table>';
|
|
} else {
|
|
output += '</div>';
|
|
}
|
|
} else if(format == "RTF" || format == "Integration") {
|
|
if(secondFieldAlign) {
|
|
// this is a really sticky issue. the below works for first fields
|
|
// that look like "[1]" and "1." otherwise, i have no idea. luckily,
|
|
// this will be good enough 99% of the time.
|
|
var alignAt = 24+maxFirstFieldLength*120;
|
|
|
|
if(secondFieldAlign == "margin") {
|
|
firstLineIndent -= alignAt;
|
|
tabStop = 0;
|
|
} else {
|
|
indent += alignAt;
|
|
firstLineIndent = -indent;
|
|
tabStop = indent;
|
|
}
|
|
}
|
|
|
|
preamble += "\\li"+indent+" \\fi"+firstLineIndent+" ";
|
|
if(format == "Integration") {
|
|
preamble += "\\sl"+lineSpacing+" ";
|
|
} else if(format == "RTF" && lineSpacing != 1) {
|
|
preamble += "\\sl"+(240*lineSpacing)+" \\slmult1 ";
|
|
}
|
|
|
|
if(tabStop !== null) {
|
|
preamble += "\\tx"+tabStop+" ";
|
|
}
|
|
preamble += "\r\n";
|
|
|
|
// drop last returns
|
|
output = output.substr(0, output.length-returnChars.length);
|
|
|
|
// add bracket for RTF
|
|
if(format == "RTF") output += "\\par }";
|
|
}
|
|
|
|
return preamble+output;
|
|
}
|
|
|
|
/*
|
|
* gets a term, in singular or plural form
|
|
*/
|
|
Zotero.CSL.prototype._getTerm = function(term, plural, form, includePeriod) {
|
|
if(!form) {
|
|
form = "long";
|
|
}
|
|
|
|
if(!this._terms[form] || !this._terms[form][term]) {
|
|
if(form == "verb-short") {
|
|
return this._getTerm(term, plural, "verb");
|
|
} else if(form == "symbol") {
|
|
return this._getTerm(term, plural, "short");
|
|
} else if(form != "long") {
|
|
return this._getTerm(term, plural, "long");
|
|
} else {
|
|
Zotero.debug("CSL: WARNING: could not find term \""+term+'"');
|
|
return "";
|
|
}
|
|
}
|
|
|
|
var term;
|
|
if(typeof(this._terms[form][term]) == "object") { // singular and plural forms
|
|
// are available
|
|
if(plural) {
|
|
term = this._terms[form][term][1];
|
|
} else {
|
|
term = this._terms[form][term][0];
|
|
}
|
|
} else {
|
|
term = this._terms[form][term];
|
|
}
|
|
|
|
if((form == "short" || form == "verb-short") && includePeriod) {
|
|
term += ".";
|
|
}
|
|
|
|
return term;
|
|
}
|
|
|
|
/*
|
|
* process creator objects; if someone had a creator model that handled
|
|
* non-Western names better than ours, this would be the function to change
|
|
*/
|
|
Zotero.CSL.prototype._processNames = function(item, element, formattedString, context, citationItem, variables) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
var children = element.children();
|
|
if(!children.length()) return false;
|
|
var variableSucceeded = false;
|
|
|
|
for(var j=0; j<variables.length; j++) {
|
|
var success = false;
|
|
var newString = formattedString.clone();
|
|
|
|
if(formattedString.format != "Sort" && variables[j] == "author" && context
|
|
&& context.option.(@name == "subsequent-author-substitute").length()
|
|
&& item.getProperty("subsequent-author-substitute")
|
|
&& context.localName() == "bibliography") {
|
|
newString.append(context.option.(@name == "subsequent-author-substitute").@value.toString());
|
|
success = true;
|
|
} else {
|
|
var creators = item.getNames(variables[j]);
|
|
|
|
if(creators && creators.length) {
|
|
var maxCreators = creators.length;
|
|
|
|
for each(var child in children) {
|
|
if(child.namespace() != Zotero.CSL.Global.ns) continue;
|
|
|
|
var name = child.localName();
|
|
if(name == "name") {
|
|
var useEtAl = false;
|
|
|
|
if(context) {
|
|
// figure out if we need to use "et al"
|
|
var etAlMin = context.option.(@name == "et-al-min").@value.toString();
|
|
var etAlUseFirst = context.option.(@name == "et-al-use-first").@value.toString();
|
|
|
|
if(citationItem && citationItem.position
|
|
&& citationItem.position >= Zotero.CSL.POSITION_SUBSEQUENT) {
|
|
if(context.option.(@name == "et-al-subsequent-min").length()) {
|
|
etAlMin = context.option.(@name == "et-al-subsequent-min").@value.toString();
|
|
}
|
|
if(context.option.(@name == "et-al-subsequent-use-first").length()) {
|
|
etAlUseFirst = context.option.(@name == "et-al-subsequent-use-first").@value.toString();
|
|
}
|
|
}
|
|
|
|
if(etAlMin && etAlUseFirst && maxCreators >= parseInt(etAlMin, 10)) {
|
|
etAlUseFirst = parseInt(etAlUseFirst, 10);
|
|
if(etAlUseFirst != maxCreators) {
|
|
maxCreators = etAlUseFirst;
|
|
useEtAl = true;
|
|
}
|
|
}
|
|
|
|
// add additional names to disambiguate
|
|
if(variables[j] == "author" && useEtAl) {
|
|
var disambigNames = item.getProperty("disambiguate-add-names");
|
|
if(disambigNames != "") {
|
|
maxCreators = disambigNames;
|
|
if(disambigNames == creators.length) useEtAl = false;
|
|
}
|
|
}
|
|
|
|
if(child.@form == "short") {
|
|
var fullNames = item.getProperty("disambiguate-add-givenname").split(",");
|
|
}
|
|
}
|
|
|
|
var authorStrings = [];
|
|
var firstName, lastName;
|
|
// parse authors into strings
|
|
for(var i=0; i<maxCreators; i++) {
|
|
if(formattedString.format == "Sort") {
|
|
// for sort, we use the plain names
|
|
var name = creators[i].getNameVariable("lastName");
|
|
|
|
// cut off lowercase parts of otherwise capitalized names (e.g., "de")
|
|
var lastNameParts = name.split(" ");
|
|
if(lastNameParts.length > 1 && lastNameParts[0] !== "" && lastNameParts[0].length <= 4
|
|
&& lastNameParts[0][0].toLowerCase() == lastNameParts[0][0]
|
|
&& lastNameParts[lastNameParts.length-1][0].toUpperCase() == lastNameParts[lastNameParts.length-1][0]) {
|
|
name = "";
|
|
for(var k=1; k<lastNameParts.length; k++) {
|
|
if(lastNameParts[k][0].toUpperCase() == lastNameParts[k][0]) {
|
|
name += " "+lastNameParts[k];
|
|
}
|
|
}
|
|
name = name.substr(1);
|
|
}
|
|
|
|
var firstName = creators[i].getNameVariable("firstName");
|
|
if(name && firstName) name += ", ";
|
|
name += firstName;
|
|
|
|
newString.append(name);
|
|
} else {
|
|
var firstName = "";
|
|
|
|
if(child.@form != "short" || (fullNames && fullNames[i])) {
|
|
if(child["@initialize-with"].length() && (!fullNames ||
|
|
fullNames[i] != Zotero.CSL.NAME_USE_FULL)) {
|
|
// even if initialize-with is simply an empty string, use
|
|
// initials
|
|
|
|
// use first initials
|
|
var firstNames = creators[i].getNameVariable("firstName").split(" ");
|
|
for(var k in firstNames) {
|
|
if(firstNames[k]) {
|
|
// get first initial, put in upper case, add initializeWith string
|
|
firstName += firstNames[k][0].toUpperCase()+child["@initialize-with"].toString();
|
|
}
|
|
}
|
|
|
|
if(firstName[firstName.length-1] == " ") {
|
|
firstName = firstName.substr(0, firstName.length-1);
|
|
}
|
|
} else {
|
|
firstName = creators[i].getNameVariable("firstName");
|
|
}
|
|
}
|
|
lastName = creators[i].getNameVariable("lastName");
|
|
|
|
if(child["@name-as-sort-order"].length()
|
|
&& ((i == 0 && child["@name-as-sort-order"] == "first")
|
|
|| child["@name-as-sort-order"] == "all")
|
|
&& child["@sort-separator"].length()) {
|
|
// if this is the first author and name-as-sort="first"
|
|
// or if this is a subsequent author and name-as-sort="all"
|
|
// then the name gets inverted
|
|
authorStrings.push(lastName+(firstName ? child["@sort-separator"].toString()+firstName : ""));
|
|
} else {
|
|
authorStrings.push((firstName ? firstName+" " : "")+lastName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(formattedString.format != "Sort") {
|
|
// figure out if we need an "and" or an "et al"
|
|
var joinString = (child["@delimiter"].length() ? child["@delimiter"].toString() : ", ");
|
|
if(creators.length > 1) {
|
|
if(useEtAl) { // multiple creators and need et al
|
|
authorStrings.push(this._getTerm("et-al"));
|
|
} else { // multiple creators but no et al
|
|
// add and to last creator
|
|
if(child["@and"].length()) {
|
|
if(child["@and"] == "symbol") {
|
|
var and = "&"
|
|
} else if(child["@and"] == "text") {
|
|
var and = this._getTerm("and");
|
|
}
|
|
|
|
authorStrings[maxCreators-1] = and+" "+authorStrings[maxCreators-1];
|
|
}
|
|
}
|
|
|
|
// check whether to use a serial comma
|
|
if((authorStrings.length == 2 && (child["@delimiter-precedes-last"] != "always" || useEtAl)) ||
|
|
(authorStrings.length > 2 && child["@delimiter-precedes-last"] == "never")) {
|
|
var lastString = authorStrings.pop();
|
|
authorStrings[authorStrings.length-1] = authorStrings[authorStrings.length-1]+" "+lastString;
|
|
}
|
|
}
|
|
newString.append(authorStrings.join(joinString), child);
|
|
}
|
|
} else if(formattedString.format != "Sort" &&
|
|
name == "label" && variables[j] != "author") {
|
|
newString.append(this._getTerm(variables[j], (maxCreators != 1), child["@form"].toString(), child["@include-period"] == "true"), child);
|
|
}
|
|
}
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if(success) {
|
|
variableSucceeded = true;
|
|
formattedString.concat(newString);
|
|
}
|
|
}
|
|
|
|
return variableSucceeded;
|
|
}
|
|
|
|
/*
|
|
* processes an element from a (pre-processed) item into text
|
|
*/
|
|
Zotero.CSL.prototype._processElements = function(item, element, formattedString,
|
|
context, citationItem, ignore, isSingle) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
if(!ignore) {
|
|
ignore = [[], []];
|
|
// ignore[0] is for variables; ignore[1] is for macros
|
|
}
|
|
|
|
var dataAppended = false;
|
|
|
|
if(isSingle) {
|
|
// handle single elements
|
|
var numberOfChildren = 1;
|
|
var children = [element];
|
|
} else {
|
|
// accept groups of elements by default
|
|
var children = element.children();
|
|
var numberOfChildren = children.length();
|
|
var lastChild = children.length()-1;
|
|
}
|
|
|
|
for(var i=0; i<numberOfChildren; i++) {
|
|
var child = children[i];
|
|
if(child.namespace() != Zotero.CSL.Global.ns) continue;
|
|
var name = child.localName();
|
|
|
|
if(name == "text") {
|
|
if(child["@term"].length()) {
|
|
var term = this._getTerm(child["@term"].toString(), child.@plural == "true", child.@form.toString(), child["@include-period"] == "true");
|
|
if(term) {
|
|
formattedString.append(term, child);
|
|
}
|
|
} else if(child.@variable.length()) {
|
|
var form = child.@form.toString();
|
|
var variables = child["@variable"].toString().split(" ");
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = false;
|
|
|
|
for(var j=0; j<variables.length; j++) {
|
|
if(ignore[0][variables[j]]) continue;
|
|
|
|
if(variables[j] == "locator") {
|
|
// special case for locator
|
|
var text = citationItem && citationItem.locator ? citationItem.locator : "";
|
|
} else if(citationItem && citationItem._csl && citationItem._csl[variables[j]]) {
|
|
// override if requested
|
|
var text = citationItem._csl[variables[j]];
|
|
} else if(variables[j] == "citation-number") {
|
|
// special case for citation-number
|
|
var text = item.getProperty("citation-number");
|
|
} else {
|
|
var text = item.getVariable(variables[j], form);
|
|
}
|
|
|
|
if(text) {
|
|
newString.append(text);
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if(success) {
|
|
formattedString.concat(newString, child);
|
|
dataAppended = true;
|
|
}
|
|
} else if(child.@macro.length()) {
|
|
var macro = this._csl.macro.(@name == child.@macro);
|
|
if(!macro.length()) throw "CSL: style references undefined macro " + child.@macro;
|
|
|
|
// If not ignored (bc already used as a substitution)
|
|
if(!ignore[1][child.@macro.toString()]) {
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = this._processElements(item, macro, newString,
|
|
context, citationItem, ignore);
|
|
if(success) dataAppended = true;
|
|
formattedString.concat(newString, child);
|
|
}
|
|
} else if(child.@value.length()) {
|
|
formattedString.append(child.@value.toString(), child);
|
|
}
|
|
} else if(name == "number") {
|
|
if(child.@variable.length()) {
|
|
var form = child.@form.toString();
|
|
var variables = child["@variable"].toString().split(" ");
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = false;
|
|
|
|
for(var j=0; j<variables.length; j++) {
|
|
if(ignore[0][variables[j]]) continue;
|
|
|
|
var text = item.getNumericVariable(variables[j], form);
|
|
if(text) {
|
|
newString.append(text);
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if(success) {
|
|
formattedString.concat(newString, child);
|
|
dataAppended = true;
|
|
}
|
|
}
|
|
} else if(name == "label") {
|
|
var form = child.@form.toString();
|
|
var variables = child["@variable"].toString().split(" ");
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = false;
|
|
|
|
for(var j=0; j<variables.length; j++) {
|
|
if(ignore[0][variables[j]]) continue;
|
|
|
|
if(variables[j] == "locator") {
|
|
// special case for locator
|
|
var term = (citationItem && citationItem.locatorType) ? citationItem.locatorType : "page";
|
|
// if "other" specified as the term, don't do anything
|
|
if(term == "other") term = false;
|
|
var value = citationItem && citationItem.locator ? citationItem.locator : false;
|
|
} else {
|
|
var term = variables[j];
|
|
var value = item.getVariable(variables[j]).toString();
|
|
}
|
|
|
|
if(term !== false && value) {
|
|
if (child["@pluralize"] == "always") {
|
|
var isPlural = true;
|
|
}
|
|
else if (child["@pluralize"] == "never") {
|
|
var isPlural = false;
|
|
}
|
|
else { // contextual
|
|
var isPlural = value.indexOf("-") != -1 || value.indexOf(",") != -1 || value.indexOf("\u2013") != -1;
|
|
}
|
|
var text = this._getTerm(term, isPlural, child.@form.toString(), child["@include-period"] == "true");
|
|
|
|
if(text) {
|
|
newString.append(text);
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(success) {
|
|
formattedString.concat(newString, child);
|
|
}
|
|
} else if(name == "names") {
|
|
var variables = child["@variable"].toString().split(" ");
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
|
|
// remove variables that aren't supposed to be there
|
|
for(var j=0; j<variables.length; j++) {
|
|
if(ignore[0][variables[j]]) {
|
|
variables.splice(j, 1);
|
|
}
|
|
}
|
|
if(!variables.length) continue;
|
|
|
|
var success = this._processNames(item, child, newString, context,
|
|
citationItem, variables);
|
|
|
|
if(!success && child.substitute.length()) {
|
|
for each(var newChild in child.substitute.children()) {
|
|
if(newChild.namespace() != Zotero.CSL.Global.ns) continue;
|
|
|
|
if(newChild.localName() == "names" && newChild.children.length() == 0) {
|
|
// apply same rules to substitute names
|
|
// with no children
|
|
var variable = newChild.@variable.toString();
|
|
variables = variable.split(" ");
|
|
success = this._processNames(item, child, newString,
|
|
context, citationItem, variables);
|
|
|
|
ignore[0][newChild.@variable.toString()] = true;
|
|
|
|
if(success) break;
|
|
} else {
|
|
if(!newChild.@suffix.length()) newChild.@suffix = element.@suffix;
|
|
if(!newChild.@prefix.length()) newChild.@prefix = element.@prefix;
|
|
|
|
success = this._processElements(item,
|
|
newChild, newString, context, citationItem, ignore, true);
|
|
|
|
// ignore if used as substitution
|
|
if(newChild.@variable.length()) {
|
|
ignore[0][newChild.@variable.toString()] = true;
|
|
} else if(newChild.@macro.length()) {
|
|
ignore[1][newChild.@macro.toString()] = true;
|
|
}
|
|
|
|
// if substitution was successful, stop
|
|
if(success) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(success) {
|
|
formattedString.concat(newString, child);
|
|
dataAppended = true;
|
|
}
|
|
} else if(name == "date") {
|
|
var variables = child["@variable"].toString().split(" ");
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = false;
|
|
|
|
for(var j=0; j<variables.length; j++) {
|
|
if(ignore[0][variables[j]]) continue;
|
|
|
|
var date = item.getDate(variables[j]);
|
|
if(!date) continue;
|
|
|
|
var variableString = formattedString.clone();
|
|
success = true;
|
|
|
|
if(formattedString.format == "Sort") {
|
|
variableString.append(date.getDateVariable("sort"));
|
|
} else {
|
|
for each(var newChild in child.children()) {
|
|
if(newChild.namespace() != Zotero.CSL.Global.ns) continue;
|
|
var newName = newChild.localName();
|
|
var newForm = newChild.@form.toString();
|
|
|
|
if(newName == "date-part") {
|
|
var part = newChild.@name.toString();
|
|
|
|
if(citationItem && citationItem._csl && citationItem._csl[variables[j]] && citationItem._csl[variables[j]][part]) {
|
|
// date is in citationItem
|
|
var string = citationItem._csl[variables[j]][part];
|
|
} else {
|
|
var string = date.getDateVariable(part);
|
|
if(string === "") continue;
|
|
|
|
if(part == "year") {
|
|
string = string.toString();
|
|
|
|
// if 4 digits and no B.C., use short form
|
|
if(newForm == "short" && string.length == 4 && !isNaN(string*1)) {
|
|
string = string.substr(2, 2);
|
|
}
|
|
|
|
var disambiguate = item.getProperty("disambiguate-add-year-suffix");
|
|
if(disambiguate && variables[j] == "issued") {
|
|
string += disambiguate;
|
|
}
|
|
} else if(part == "month") {
|
|
// if month is a numeric month, format as such
|
|
if(!isNaN(string*1)) {
|
|
if(newForm == "numeric-leading-zeros") {
|
|
string = (string+1).toString();
|
|
if(string.length == 1) {
|
|
string = "0" + string;
|
|
}
|
|
} else if(newForm == "short") {
|
|
string = this._terms["short"]["_months"][string];
|
|
} else if(newForm == "numeric") {
|
|
string = (1+string).toString();
|
|
} else {
|
|
string = this._terms["long"]["_months"][string];
|
|
}
|
|
} else if(newForm == "numeric") {
|
|
string = "";
|
|
}
|
|
} else if(part == "day") {
|
|
string = string.toString();
|
|
if(form == "numeric-leading-zeros"
|
|
&& string.length() == 1) {
|
|
string = "0" + string;
|
|
} else if (newForm == "ordinal") {
|
|
var ind = parseInt(string);
|
|
var daySuffixes = Zotero.getString("date.daySuffixes").replace(/, ?/g, "|").split("|");
|
|
string += (parseInt(ind/10)%10) == 1 ? daySuffixes[3] : (ind % 10 == 1) ? daySuffixes[0] : (ind % 10 == 2) ? daySuffixes[1] : (ind % 10 == 3) ? daySuffixes[2] : daySuffixes[3];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
variableString.append(string, newChild);
|
|
}
|
|
|
|
newString.concat(variableString);
|
|
}
|
|
|
|
formattedString.concat(newString, child);
|
|
}
|
|
|
|
if(success) {
|
|
dataAppended = true;
|
|
}
|
|
} else if(name == "group") {
|
|
var newString = formattedString.clone(child.@delimiter.toString());
|
|
var success = this._processElements(item,
|
|
child, newString, context, citationItem,
|
|
ignore);
|
|
|
|
// concat only if true data (not text element) was appended
|
|
if(success) {
|
|
formattedString.concat(newString, child);
|
|
dataAppended = true;
|
|
}
|
|
} else if(name == "choose") {
|
|
for each(var newChild in child.children()) {
|
|
if(newChild.namespace() != Zotero.CSL.Global.ns) continue;
|
|
|
|
var truthValue;
|
|
|
|
if(newChild.localName() == "else") {
|
|
// always true, if we got to this point in the loop
|
|
truthValue = true;
|
|
} else if(newChild.localName() == "if"
|
|
|| newChild.localName() == "else-if") {
|
|
|
|
var matchAny = newChild.@match == "any";
|
|
var matchNone = newChild.@match == "none";
|
|
if(matchAny) {
|
|
// if matching any, begin with false, then set to true
|
|
// if a condition is true
|
|
truthValue = false;
|
|
} else {
|
|
// if matching all, begin with true, then set to false
|
|
// if a condition is false
|
|
truthValue = true;
|
|
}
|
|
|
|
// inspect variables
|
|
var done = false;
|
|
var attributes = ["variable", "is-date", "is-numeric", "is-plural", "type", "disambiguate", "locator", "position"];
|
|
for(var k=0; !done && k<attributes.length; k++) {
|
|
var attribute = attributes[k];
|
|
|
|
if(newChild["@"+attribute].length()) {
|
|
var variables = newChild["@"+attribute].toString().split(" ");
|
|
for(var j=0; !done && j<variables.length; j++) {
|
|
var exists = false;
|
|
if(attribute == "variable") {
|
|
if(variables[j] == "locator") {
|
|
// special case for locator
|
|
exists = citationItem && citationItem.locator && citationItem.locator.length > 0
|
|
}
|
|
else if(Zotero.CSL._dateVariables[variables[j]]) {
|
|
// getDate not false/undefined
|
|
exists = !!item.getDate(variables[j]);
|
|
} else if(Zotero.CSL._namesVariables[variables[j]]) {
|
|
// getNames not false/undefined, not empty
|
|
exists = item.getNames(variables[j]);
|
|
if(exists) exists = !!exists.length;
|
|
} else {
|
|
exists = item.getVariable(variables[j]);
|
|
if (exists) exists = !!exists.length;
|
|
}
|
|
} else if (attribute == "is-numeric") {
|
|
exists = item.getNumericVariable(variables[j]);
|
|
} else if (attribute == "is-date") { // XXX - this needs improving
|
|
if (Zotero.CSL._dateVariables[variables[j]]) {
|
|
exists = !!item.getDate(variables[j]);
|
|
}
|
|
} else if(attribute == "is-plural") {
|
|
if(Zotero.CSL._namesVariables[variables[j]]) {
|
|
exists = item.getNames(variables[j]);
|
|
if(exists) exists = exists.length > 1;
|
|
} else if(variables[j] == "page" || variables[j] == "locator") {
|
|
if(variables[j] == "page") {
|
|
var value = item.getVariable("page");
|
|
} else {
|
|
var value = citationItem && citationItem.locator ? citationItem.locator : "";
|
|
}
|
|
exists = value.indexOf("-") != -1 || value.indexOf(",") != -1 || value.indexOf("\u2013") != -1;
|
|
}
|
|
} else if(attribute == "type") {
|
|
exists = item.isType(variables[j]);
|
|
} else if(attribute == "disambiguate") {
|
|
exists = (variables[j] == "true" && item.getProperty("disambiguate-condition"))
|
|
|| (variables[j] == "false" && !item.getProperty("disambiguate-condition"));
|
|
} else if(attribute == "locator") {
|
|
exists = citationItem && citationItem.locator &&
|
|
(citationItem.locatorType == variables[j]
|
|
|| (!citationItem.locatorType && variables[j] == "page"));
|
|
} else { // attribute == "position"
|
|
if(variables[j] == "first") {
|
|
exists = !citationItem
|
|
|| !citationItem.position
|
|
|| citationItem.position == Zotero.CSL.POSITION_FIRST;
|
|
} else if(variables[j] == "subsequent") {
|
|
exists = citationItem && citationItem.position >= Zotero.CSL.POSITION_SUBSEQUENT;
|
|
} else if(variables[j] == "ibid") {
|
|
exists = citationItem && citationItem.position >= Zotero.CSL.POSITION_IBID;
|
|
} else if(variables[j] == "ibid-with-locator") {
|
|
exists = citationItem && citationItem.position == Zotero.CSL.POSITION_IBID_WITH_LOCATOR;
|
|
}
|
|
}
|
|
|
|
if(matchAny) {
|
|
if(exists) {
|
|
truthValue = true;
|
|
done = true;
|
|
}
|
|
} else if(matchNone) {
|
|
if(exists) {
|
|
truthValue = false;
|
|
done = true;
|
|
}
|
|
} else if(!exists) {
|
|
truthValue = false;
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(truthValue) {
|
|
// if true, process
|
|
var newString = formattedString.clone(newChild.@delimiter.toString());
|
|
var success = this._processElements(item, newChild,
|
|
newString, context, citationItem, ignore);
|
|
if(success) dataAppended = true;
|
|
formattedString.concat(newString, child);
|
|
|
|
// then break
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
Zotero.debug("CSL: WARNING: could not add element "+name);
|
|
}
|
|
}
|
|
|
|
return dataAppended;
|
|
}
|
|
|
|
/*
|
|
* Compares two items, in order to sort the reference list
|
|
* Returns -1 if A comes before B, 1 if B comes before A, or 0 if they are equal
|
|
*/
|
|
Zotero.CSL.prototype._compareItem = function(a, b, context, cache) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
var sortA = [];
|
|
var sortB = [];
|
|
|
|
var aID = a.id;
|
|
var bID = b.id;
|
|
|
|
// author
|
|
if(context.sort.key.length()) {
|
|
var keyA, keyB;
|
|
for each(var key in context.sort.key) {
|
|
if(key.@macro.length()) {
|
|
var aCacheKey = aID+"-macro-"+key.@macro;
|
|
var bCacheKey = bID+"-macro-"+key.@macro;
|
|
|
|
if(cache[aCacheKey]) {
|
|
keyA = cache[aCacheKey];
|
|
} else {
|
|
keyA = new Zotero.CSL.SortString();
|
|
this._processElements(a, this._csl.macro.(@name == key.@macro), keyA);
|
|
cache[aCacheKey] = keyA;
|
|
}
|
|
|
|
if(cache[bCacheKey]) {
|
|
keyB = cache[bCacheKey];
|
|
} else {
|
|
keyB = new Zotero.CSL.SortString();
|
|
this._processElements(b, this._csl.macro.(@name == key.@macro), keyB);
|
|
cache[bCacheKey] = keyB;
|
|
}
|
|
} else if(key.@variable.length()) {
|
|
var variable = key.@variable.toString();
|
|
var keyA = new Zotero.CSL.SortString();
|
|
var keyB = new Zotero.CSL.SortString();
|
|
|
|
if(Zotero.CSL._dateVariables[variable]) { // date
|
|
var date = a.getDate(variable);
|
|
if(date) keyA.append(date.getDateVariable("sort"));
|
|
date = b.getDate(variable);
|
|
if(date) keyB.append(date.getDateVariable("sort"));
|
|
} else if(Zotero.CSL._namesVariables[key.@variable]) { // names
|
|
var element = <names><name/></names>;
|
|
element.setNamespace(Zotero.CSL.Global.ns);
|
|
|
|
this._processNames(a, element, keyA, context, null, [variable]);
|
|
this._processNames(b, element, keyB, context, null, [variable]);
|
|
} else { // text
|
|
if(variable == "citation-number") {
|
|
keyA.append(a.getProperty(variable));
|
|
keyB.append(b.getProperty(variable));
|
|
} else {
|
|
keyA.append(a.getVariable(variable));
|
|
keyB.append(b.getVariable(variable));
|
|
}
|
|
}
|
|
}
|
|
|
|
var compare = keyA.compare(keyB);
|
|
if(key.@sort == "descending") { // the compare method sorts ascending
|
|
// so we sort descending by reversing it
|
|
if(compare < 1) return 1;
|
|
if(compare > 1) return -1;
|
|
} else if(compare != 0) {
|
|
return compare;
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort by index in document
|
|
var aIndex = a.getProperty("index");
|
|
var bIndex = b.getProperty("index");
|
|
if(aIndex !== "" && (bIndex === "" || aIndex < bIndex)) {
|
|
return -1;
|
|
} else if(aIndex != bIndex) {
|
|
return 1;
|
|
}
|
|
|
|
// sort by old index (to make this a stable sort)
|
|
var aOldIndex = a.getProperty("oldIndex");
|
|
var bOldIndex = b.getProperty("oldIndex");
|
|
if(aOldIndex < bOldIndex) {
|
|
return -1;
|
|
} else if(aOldIndex != bOldIndex) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sorts a list of items, keeping a cache of processed keys
|
|
**/
|
|
Zotero.CSL.prototype.cachedSort = function(items, context, field) {
|
|
var me = this;
|
|
var cache = new Object();
|
|
|
|
for(var i=0; i<items.length; i++) {
|
|
if(items[i].setProperty) items[i].setProperty("oldIndex", i);
|
|
}
|
|
|
|
if(field) {
|
|
var newItems = items.sort(function(a, b) {
|
|
return me._compareItem(a[field], b[field], context, cache);
|
|
});
|
|
} else {
|
|
var newItems = items.sort(function(a, b) {
|
|
return me._compareItem(a, b, context, cache);
|
|
});
|
|
}
|
|
|
|
delete cache;
|
|
return newItems;
|
|
}
|
|
|
|
Zotero.CSL.prototype.getEqualCitations = function(items) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
var citationsEqual = [];
|
|
|
|
if(items) {
|
|
var context = this._csl.citation;
|
|
|
|
var string = new Zotero.CSL.FormattedString(context.options, "Text");
|
|
this._processElements(items[0], context.layout, string,
|
|
context, "subsequent");
|
|
var lastString = string.get();
|
|
|
|
for(var i=1; i<items.length; i++) {
|
|
string = new Zotero.CSL.FormattedString(context.option, "Text");
|
|
this._processElements(items[i], context.layout, string,
|
|
context, "subsequent");
|
|
string = string.get();
|
|
|
|
citationsEqual[i] = string == lastString;
|
|
lastString = string;
|
|
}
|
|
}
|
|
|
|
return citationsEqual;
|
|
}
|
|
|
|
/*
|
|
* Compares two citations; returns true if they are different, false if they are equal
|
|
*/
|
|
Zotero.CSL.prototype.compareCitations = function(a, b, context) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
if((!a && b) || (a && !b)) {
|
|
return true;
|
|
} else if(!a && !b) {
|
|
return false;
|
|
}
|
|
|
|
var option = (context ? context.option : null);
|
|
var aString = new Zotero.CSL.FormattedString(option, "Text");
|
|
this._processElements(a, this._csl.citation.layout, aString,
|
|
context, "subsequent");
|
|
|
|
var bString = new Zotero.CSL.FormattedString(option, "Text");
|
|
this._processElements(b, this._csl.citation.layout, bString,
|
|
context, "subsequent");
|
|
|
|
return !(aString.get() == bString.get());
|
|
}
|
|
|
|
Zotero.CSL.Global = new function() {
|
|
this.init = init;
|
|
this.getMonthStrings = getMonthStrings;
|
|
this.getLocatorStrings = getLocatorStrings;
|
|
this.cleanXML = cleanXML;
|
|
this.parseLocales = parseLocales;
|
|
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
this.ns = "http://purl.org/net/xbiblio/csl";
|
|
|
|
this.__defineGetter__("locale", function() {
|
|
Zotero.CSL.Global.init()
|
|
return Zotero.CSL.Global._xmlLang;
|
|
});
|
|
this.collation = Components.classes["@mozilla.org/intl/collation-factory;1"]
|
|
.getService(Components.interfaces.nsICollationFactory)
|
|
.CreateCollation(Components.classes["@mozilla.org/intl/nslocaleservice;1"]
|
|
.getService(Components.interfaces.nsILocaleService)
|
|
.getApplicationLocale());
|
|
|
|
var locatorTypeTerms = ["page", "book", "chapter", "column", "figure", "folio",
|
|
"issue", "line", "note", "opus", "paragraph", "part", "section",
|
|
"volume", "verse"];
|
|
|
|
/*
|
|
* initializes CSL interpreter
|
|
*/
|
|
function init() {
|
|
if(!Zotero.CSL.Global._xmlLang) {
|
|
var prefix = "chrome://zotero/content/locale/csl/locales-";
|
|
var ext = ".xml";
|
|
|
|
// If explicit bib locale, try to use that
|
|
var bibLocale = Zotero.Prefs.get('export.bibliographyLocale');
|
|
if (bibLocale) {
|
|
var loc = bibLocale;
|
|
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
|
createInstance();
|
|
req.open("GET", prefix + loc + ext, false);
|
|
req.overrideMimeType("text/plain");
|
|
var fail = false;
|
|
try {
|
|
req.send(null);
|
|
}
|
|
catch (e) {
|
|
fail = true;
|
|
}
|
|
|
|
if (!fail) {
|
|
Zotero.CSL.Global._xmlLang = loc;
|
|
var xml = req.responseText;
|
|
}
|
|
}
|
|
|
|
// If no or invalid bib locale, try Firefox locale
|
|
if (!xml) {
|
|
var loc = Zotero.locale;
|
|
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
|
createInstance();
|
|
req.open("GET", prefix + loc + ext, false);
|
|
req.overrideMimeType("text/plain");
|
|
var fail = false;
|
|
try {
|
|
req.send(null);
|
|
}
|
|
catch (e) {
|
|
fail = true;
|
|
}
|
|
|
|
if (!fail) {
|
|
Zotero.CSL.Global._xmlLang = loc;
|
|
var xml = req.responseText;
|
|
}
|
|
}
|
|
|
|
// Fall back to en-US if no locales.xml
|
|
if (!xml) {
|
|
var loc = 'en-US';
|
|
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
|
createInstance();
|
|
req.open("GET", prefix + loc + ext, false);
|
|
req.overrideMimeType("text/plain");
|
|
req.send(null);
|
|
|
|
Zotero.CSL.Global._xmlLang = loc;
|
|
var xml = req.responseText;
|
|
}
|
|
|
|
Zotero.debug('CSL: Using ' + loc + ' as bibliography locale');
|
|
|
|
// get default terms
|
|
var locales = new XML(Zotero.CSL.Global.cleanXML(xml));
|
|
Zotero.CSL.Global._defaultTerms = Zotero.CSL.Global.parseLocales(locales, true);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* returns an array of short or long month strings
|
|
*/
|
|
function getMonthStrings(form) {
|
|
Zotero.CSL.Global.init();
|
|
return Zotero.CSL.Global._defaultTerms[form]["_months"];
|
|
}
|
|
|
|
/*
|
|
* returns an array of short or long locator strings
|
|
*/
|
|
function getLocatorStrings(form) {
|
|
if(!form) form = "long";
|
|
|
|
Zotero.CSL.Global.init();
|
|
var locatorStrings = new Object();
|
|
for(var i=0; i<locatorTypeTerms.length; i++) {
|
|
var term = locatorTypeTerms[i];
|
|
var termKey = term;
|
|
if(term == "page") termKey = "";
|
|
locatorStrings[termKey] = Zotero.CSL.Global._defaultTerms[form][term];
|
|
|
|
if(!locatorStrings[termKey] && form == "symbol") {
|
|
locatorStrings[termKey] = Zotero.CSL.Global._defaultTerms["short"][term];
|
|
}
|
|
if(!locatorStrings[termKey]) {
|
|
locatorStrings[termKey] = Zotero.CSL.Global._defaultTerms["long"][term];
|
|
}
|
|
|
|
// use singular form
|
|
if(typeof(locatorStrings[termKey]) == "object") locatorStrings[termKey] = locatorStrings[termKey][0];
|
|
}
|
|
return locatorStrings;
|
|
}
|
|
|
|
/*
|
|
* removes parse instructions from XML
|
|
*/
|
|
function cleanXML(xml) {
|
|
return xml.replace(/<\?[^>]*\?>/g, "");
|
|
}
|
|
|
|
/*
|
|
* parses locale strings into an array;
|
|
*/
|
|
function parseLocales(termXML, ignoreLang) {
|
|
// return defaults if there are no terms
|
|
if(!termXML.length()) {
|
|
return (Zotero.CSL.Global._defaultTerms ? Zotero.CSL.Global._defaultTerms : {});
|
|
}
|
|
|
|
var xml = new Namespace("http://www.w3.org/XML/1998/namespace");
|
|
|
|
if(ignoreLang) {
|
|
// ignore lang if loaded from chrome
|
|
locale = termXML.locale[0];
|
|
} else {
|
|
// get proper locale
|
|
var locale = termXML.locale.(@xml::lang == Zotero.CSL.Global._xmlLang);
|
|
if(!locale.length()) {
|
|
var xmlLang = Zotero.CSL.Global._xmlLang.substr(0, 2);
|
|
locale = termXML.locale.(@xml::lang == xmlLang);
|
|
}
|
|
if(!locale.length()) {
|
|
// return defaults if there are no locales
|
|
return (Zotero.CSL.Global._defaultTerms ? Zotero.CSL.Global._defaultTerms : {});
|
|
}
|
|
}
|
|
|
|
var termArray = new Object();
|
|
termArray["default"] = new Object();
|
|
|
|
if(Zotero.CSL.Global._defaultTerms) {
|
|
// ugh. copy default array. javascript dumb.
|
|
for(var i in Zotero.CSL.Global._defaultTerms) {
|
|
termArray[i] = new Object();
|
|
for(var j in Zotero.CSL.Global._defaultTerms[i]) {
|
|
if(typeof(Zotero.CSL.Global._defaultTerms[i]) == "object") {
|
|
termArray[i][j] = [Zotero.CSL.Global._defaultTerms[i][j][0],
|
|
Zotero.CSL.Global._defaultTerms[i][j][1]];
|
|
} else {
|
|
termArray[i][j] = Zotero.CSL.Global_defaultTerms[i][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// loop through terms
|
|
for each(var term in locale.term) {
|
|
var name = term.@name.toString();
|
|
if(!name) {
|
|
throw("CSL: citations cannot be generated: no name defined on term in locales.xml");
|
|
}
|
|
// unless otherwise specified, assume "long" form
|
|
var form = term.@form.toString();
|
|
if(!form) {
|
|
var form = "long";
|
|
}
|
|
if(!termArray[form]) {
|
|
termArray[form] = new Object();
|
|
}
|
|
|
|
var single = term.single.text().toString();
|
|
var multiple = term.multiple.text().toString();
|
|
if(single || multiple) {
|
|
if((single && multiple) // if there's both elements or
|
|
|| !termArray[form][name]) { // no previously defined value
|
|
termArray[form][name] = [single, multiple];
|
|
} else {
|
|
if(typeof(termArray[name]) != "object") {
|
|
// if old object was just a single value, make it two copies
|
|
termArray[form][name] = [termArray[form][name], termArray[form][name]];
|
|
}
|
|
|
|
// redefine either single or multiple
|
|
if(single) {
|
|
termArray[form][name][0] = single;
|
|
} else {
|
|
termArray[form][name][1] = multiple;
|
|
}
|
|
}
|
|
} else {
|
|
if(name.substr(0, 6) == "month-") {
|
|
// place months into separate array
|
|
if(!termArray[form]["_months"]) {
|
|
termArray[form]["_months"] = new Array();
|
|
}
|
|
var monthIndex = parseInt(name.substr(6),10)-1;
|
|
var term = term.text().toString();
|
|
termArray[form]["_months"][monthIndex] = term[0].toUpperCase()+term.substr(1).toLowerCase();
|
|
} else {
|
|
termArray[form][name] = term.text().toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure parity between long and short months
|
|
var longMonths = termArray["long"]["_months"];
|
|
var shortMonths = termArray["short"]["_months"];
|
|
for(var i=0; i<longMonths.length; i++) {
|
|
if(!shortMonths[i]) {
|
|
shortMonths[i] = longMonths[i];
|
|
}
|
|
}
|
|
|
|
return termArray;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* the CitationItem object represents an individual source within a citation.
|
|
*
|
|
* PROPERTIES:
|
|
* prefix
|
|
* suffix
|
|
* locatorType
|
|
* locator
|
|
* suppressAuthor
|
|
* item
|
|
* itemID
|
|
*/
|
|
Zotero.CSL.CitationItem = function(item) {
|
|
if(item) {
|
|
this.item = item;
|
|
this.itemID = item.id;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* the Citation object represents a citation.
|
|
*/
|
|
Zotero.CSL.Citation = function(citationItems, csl) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
if(csl) {
|
|
this._csl = csl;
|
|
this._citation = csl._csl.citation;
|
|
this.sortable = this._citation.sort.key.length();
|
|
} else {
|
|
this.sortable = false;
|
|
}
|
|
|
|
this.citationItems = [];
|
|
if(citationItems) this.add(citationItems);
|
|
|
|
// reserved for application use
|
|
this.properties = {};
|
|
}
|
|
|
|
/*
|
|
* sorts a citation
|
|
*/
|
|
Zotero.CSL.Citation.prototype.sort = function() {
|
|
if(this.sortable) {
|
|
this.citationItems = this._csl.cachedSort(this.citationItems, this._citation, "item");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* adds a citationItem to a citation
|
|
*/
|
|
Zotero.CSL.Citation.prototype.add = function(citationItems) {
|
|
for(var i=0; i<citationItems.length; i++) {
|
|
var citationItem = citationItems[i];
|
|
|
|
if(citationItem instanceof Zotero.CSL.Item
|
|
|| citationItem instanceof Zotero.Item) {
|
|
this.citationItems.push(new Zotero.CSL.CitationItem(citationItem));
|
|
} else {
|
|
this.citationItems.push(citationItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* removes a citationItem from a citation
|
|
*/
|
|
Zotero.CSL.Citation.prototype.remove = function(citationItems) {
|
|
for each(var citationItem in citationItems){
|
|
var index = this.citationItems.indexOf(citationItem);
|
|
if(index == -1) throw "Zotero.CSL.Citation: tried to remove an item not in citation";
|
|
this.citationItems.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* copies a citation
|
|
*/
|
|
Zotero.CSL.Citation.prototype.clone = function() {
|
|
var clone = new Zotero.CSL.Citation();
|
|
|
|
// copy items
|
|
for(var i=0; i<this.citationItems.length; i++) {
|
|
var oldCitationItem = this.citationItems[i];
|
|
var newCitationItem = new Zotero.CSL.CitationItem();
|
|
for(var key in oldCitationItem) {
|
|
newCitationItem[key] = oldCitationItem[key];
|
|
}
|
|
clone.citationItems.push(newCitationItem);
|
|
}
|
|
|
|
// copy properties
|
|
for(var key in this.properties) {
|
|
clone.properties[key] = this.properties[key];
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
/*
|
|
* This is an item wrapper class for Zotero items. If converting this code to
|
|
* work with another application, this is what needs changing. Potentially, this
|
|
* function could accept an ID or an XML data structure instead of an actual
|
|
* item, provided it implements the same public interfaces (those not beginning
|
|
* with "_") are implemented.
|
|
*/
|
|
Zotero.CSL.Item = function(item) {
|
|
if(item instanceof Zotero.Item) {
|
|
this.zoteroItem = item;
|
|
} else if(parseInt(item, 10) == item) {
|
|
// is an item ID
|
|
this.zoteroItem = Zotero.Items.get(item);
|
|
}
|
|
|
|
if(!this.zoteroItem) {
|
|
throw "Zotero.CSL.Item called to wrap a non-item";
|
|
}
|
|
|
|
this.id = this.zoteroItem.id;
|
|
this.key = this.zoteroItem.key;
|
|
|
|
// don't return URL or accessed information for journal articles if a
|
|
// pages field exists
|
|
var itemType = Zotero.ItemTypes.getName(this.zoteroItem.itemTypeID);
|
|
if(!Zotero.Prefs.get("export.citePaperJournalArticleURL")
|
|
&& ["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1
|
|
&& this.zoteroItem.getField("pages")) {
|
|
this._ignoreURL = true;
|
|
}
|
|
|
|
this._properties = {};
|
|
this._refreshItem();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns some identifier for the item. Used to create citations. In Zotero,
|
|
* this is the item ID
|
|
*
|
|
* @deprecated
|
|
**/
|
|
Zotero.CSL.Item.prototype.getID = function() {
|
|
Zotero.debug("Zotero.CSL.Item.getID() deprecated; use Zotero.CSL.Item.id");
|
|
return this.zoteroItem.id;
|
|
}
|
|
|
|
/**
|
|
* Refreshes item if it has been modified
|
|
*/
|
|
Zotero.CSL.Item.prototype._refreshItem = function() {
|
|
var previousChanged = this._lastChanged;
|
|
this._lastChanged = this.zoteroItem.getField("dateModified", false, true);
|
|
|
|
if(this._lastChanged != previousChanged) {
|
|
this._names = undefined;
|
|
this._dates = {};
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mappings for names
|
|
*/
|
|
Zotero.CSL.Item._zoteroNameMap = {
|
|
"collection-editor":"seriesEditor"
|
|
}
|
|
|
|
/*
|
|
* Gets an array of Item.Name objects for a variable.
|
|
*/
|
|
Zotero.CSL.Item.prototype.getNames = function(variable) {
|
|
var field = Zotero.CSL.Item._zoteroNameMap[variable];
|
|
if (field) variable = field;
|
|
this._refreshItem();
|
|
if(!this._names) {
|
|
this._separateNames();
|
|
}
|
|
|
|
if(this._names[variable]) {
|
|
return this._names[variable];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/*
|
|
* Gets an Item.Date object for a specific type.
|
|
*/
|
|
Zotero.CSL.Item.prototype.getDate = function(variable) {
|
|
// ignore accessed date
|
|
if(this._ignoreURL && variable == "accessed") return false;
|
|
|
|
// load date variable if possible
|
|
this._refreshItem();
|
|
if(this._dates[variable] == undefined) {
|
|
this._createDate(variable);
|
|
}
|
|
|
|
if(this._dates[variable]) return this._dates[variable];
|
|
return false;
|
|
}
|
|
|
|
Zotero.CSL.Item._zoteroFieldMap = {
|
|
"long":{
|
|
"title":"title",
|
|
"container-title":["publicationTitle", "reporter", "code"], /* reporter and code should move to SQL mapping tables */
|
|
"collection-title":["seriesTitle", "series"],
|
|
"collection-number":"seriesNumber",
|
|
"publisher":["publisher", "distributor"], /* distributor should move to SQL mapping tables */
|
|
"publisher-place":"place",
|
|
"page":"pages",
|
|
"volume":"volume",
|
|
"issue":"issue",
|
|
"number-of-volumes":"numberOfVolumes",
|
|
"edition":"edition",
|
|
"version":"version",
|
|
"section":"section",
|
|
"genre":["type", "artworkSize"], /* artworkSize should move to SQL mapping tables, or added as a CSL variable */
|
|
"medium":"medium",
|
|
"archive":"repository",
|
|
"archive_location":"archiveLocation",
|
|
"event":["meetingName", "conferenceName"], /* these should be mapped to the same base field in SQL mapping tables */
|
|
"event-place":"place",
|
|
"abstract":"abstractNote",
|
|
"URL":"url",
|
|
"DOI":"DOI",
|
|
"ISBN" : "ISBN",
|
|
"call-number":"callNumber",
|
|
"note":"extra",
|
|
"number":"number",
|
|
"references":"history"
|
|
},
|
|
"short":{
|
|
"title":["shortTitle", "title"],
|
|
"container-title":"journalAbbreviation",
|
|
"genre":["shortTitle", "type"] /* needed for subsequent citations of items with no title */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Gets a text object for a specific type.
|
|
*/
|
|
Zotero.CSL.Item.prototype.getVariable = function(variable, form) {
|
|
if(!Zotero.CSL.Item._zoteroFieldMap["long"][variable]) return "";
|
|
|
|
// ignore URL
|
|
if(this._ignoreURL && variable == "URL") return ""
|
|
|
|
var zoteroFields = [];
|
|
var field;
|
|
|
|
if(form == "short" && Zotero.CSL.Item._zoteroFieldMap["short"][variable]) {
|
|
field = Zotero.CSL.Item._zoteroFieldMap["short"][variable];
|
|
if(typeof field == "string") {
|
|
zoteroFields.push(field);
|
|
} else {
|
|
zoteroFields = zoteroFields.concat(field);
|
|
}
|
|
}
|
|
|
|
field = Zotero.CSL.Item._zoteroFieldMap["long"][variable];
|
|
if(typeof field == "string") {
|
|
zoteroFields.push(field);
|
|
} else {
|
|
zoteroFields = zoteroFields.concat(field);
|
|
}
|
|
|
|
for each(var zoteroField in zoteroFields) {
|
|
var value = this.zoteroItem.getField(zoteroField, false, true);
|
|
if(value != "") return value;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
* convert a number into a ordinal number 1st, 2nd, 3rd etc.
|
|
*/
|
|
Zotero.CSL.Item.prototype.makeOrdinal = function(value) {
|
|
var ind = parseInt(value);
|
|
var daySuffixes = Zotero.getString("date.daySuffixes").replace(/, ?/g, "|").split("|");
|
|
value += (parseInt(ind/10)%10) == 1 ? daySuffixes[3] : (ind % 10 == 1) ? daySuffixes[0] : (ind % 10 == 2) ? daySuffixes[1] : (ind % 10 == 3) ? daySuffixes[2] : daySuffixes[3];
|
|
return value;
|
|
}
|
|
|
|
|
|
Zotero.CSL.Item._zoteroRomanNumerals = {
|
|
"0" : [ "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" ],
|
|
"1" : [ "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" ],
|
|
"2" : [ "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" ],
|
|
"3" : [ "", "m", "mm", "mmm", "mmmm", "mmmmm"],
|
|
}
|
|
|
|
/*
|
|
* Convert a number into a roman numeral.
|
|
*/
|
|
Zotero.CSL.Item.prototype.makeRoman = function(value) {
|
|
|
|
var number = parseInt(value);
|
|
var result = "";
|
|
if (number > 5000) return "";
|
|
var thousands = parseInt(number/1000);
|
|
if (thousands > 0) {
|
|
result += Zotero.CSL.Item._zoteroRomanNumerals[3][thousands];
|
|
}
|
|
number = number % 1000;
|
|
var hundreds = parseInt(number/100);
|
|
if (hundreds > 0) {
|
|
result += Zotero.CSL.Item._zoteroRomanNumerals[2][hundreds];
|
|
}
|
|
number = number % 100;
|
|
var tens = parseInt(number/10);
|
|
if (tens > 0) {
|
|
result += Zotero.CSL.Item._zoteroRomanNumerals[1][tens];
|
|
}
|
|
number = number % 10;
|
|
if (number > 0) {
|
|
result += Zotero.CSL.Item._zoteroRomanNumerals[0][number];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Zotero.CSL.Item._zoteroNumberFieldMap = {
|
|
"volume":"volume",
|
|
"issue":"issue",
|
|
"number-of-volumes":"numberOfVolumes",
|
|
"edition":"edition",
|
|
"number":"number"
|
|
}
|
|
/*
|
|
* Gets a numeric object for a specific type. <number variable="edition" form="roman"/>
|
|
*/
|
|
Zotero.CSL.Item.prototype.getNumericVariable = function(variable, form) {
|
|
|
|
if(!Zotero.CSL.Item._zoteroNumberFieldMap[variable]) return "";
|
|
|
|
var zoteroFields = [];
|
|
var field;
|
|
|
|
field = Zotero.CSL.Item._zoteroNumberFieldMap[variable];
|
|
if(typeof field == "string") {
|
|
zoteroFields.push(field);
|
|
} else {
|
|
zoteroFields = zoteroFields.concat(field);
|
|
}
|
|
|
|
var matches;
|
|
for each(var zoteroField in zoteroFields) {
|
|
var value = this.zoteroItem.getField(zoteroField, false, true);
|
|
var matches;
|
|
if(value != "" && (matches = value.toString().match(Zotero.CSL._numberRegexp)) ) {
|
|
value = matches[0];
|
|
if (form == "ordinal") {
|
|
return this.makeOrdinal(value);
|
|
}
|
|
else if (form == "roman") {
|
|
return this.makeRoman(value);
|
|
}
|
|
else
|
|
return value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
* Sets an item-specific property to a given value.
|
|
*/
|
|
Zotero.CSL.Item.prototype.setProperty = function(property, value) {
|
|
this._properties[property] = value;
|
|
}
|
|
|
|
/*
|
|
* Sets an item-specific property to a given value.
|
|
*/
|
|
Zotero.CSL.Item.prototype.getProperty = function(property, value) {
|
|
return (this._properties[property] !== undefined ? this._properties[property] : "");
|
|
}
|
|
|
|
Zotero.CSL.Item._optionalTypeMap = {
|
|
journalArticle:"article-journal",
|
|
magazineArticle:"article-magazine",
|
|
newspaperArticle:"article-newspaper",
|
|
thesis:"thesis",
|
|
conferencePaper:"paper-conference",
|
|
letter:"personal_communication",
|
|
manuscript:"manuscript",
|
|
interview:"interview",
|
|
film:"motion_picture",
|
|
artwork:"graphic",
|
|
webpage:"webpage",
|
|
report:"report",
|
|
bill:"bill",
|
|
case:"legal_case",
|
|
hearing:"bill", // ??
|
|
patent:"patent",
|
|
statute:"bill", // ??
|
|
email:"personal_communication",
|
|
map:"map",
|
|
blogPost:"webpage",
|
|
instantMessage:"personal_communication",
|
|
forumPost:"webpage",
|
|
audioRecording:"song", // ??
|
|
presentation:"speech",
|
|
videoRecording:"motion_picture",
|
|
tvBroadcast:"broadcast",
|
|
radioBroadcast:"broadcast",
|
|
podcast:"song", // ??
|
|
computerProgram:"book" // ??
|
|
};
|
|
|
|
// TODO: check with Elena/APA/MLA on this
|
|
Zotero.CSL.Item._fallbackTypeMap = {
|
|
book:"book",
|
|
bookSection:"chapter",
|
|
journalArticle:"article",
|
|
magazineArticle:"article",
|
|
newspaperArticle:"article",
|
|
thesis:"article",
|
|
encyclopediaArticle:"chapter",
|
|
dictionaryEntry:"chapter",
|
|
conferencePaper:"chapter",
|
|
letter:"article",
|
|
manuscript:"article",
|
|
interview:"article",
|
|
film:"book",
|
|
artwork:"book",
|
|
webpage:"article",
|
|
report:"book",
|
|
bill:"book",
|
|
case:"book",
|
|
hearing:"book",
|
|
patent:"article",
|
|
statute:"book",
|
|
email:"article",
|
|
map:"article",
|
|
blogPost:"article",
|
|
instantMessage:"article",
|
|
forumPost:"article",
|
|
audioRecording:"book",
|
|
presentation:"article",
|
|
videoRecording:"book",
|
|
tvBroadcast:"article",
|
|
radioBroadcast:"article",
|
|
podcast:"article",
|
|
computerProgram:"book"
|
|
};
|
|
|
|
/*
|
|
* Determines whether this item is of a given type
|
|
*/
|
|
Zotero.CSL.Item.prototype.isType = function(type) {
|
|
var zoteroType = Zotero.ItemTypes.getName(this.zoteroItem.itemTypeID);
|
|
|
|
return (Zotero.CSL.Item._optionalTypeMap[zoteroType]
|
|
&& Zotero.CSL.Item._optionalTypeMap[zoteroType] == type)
|
|
|| (Zotero.CSL.Item._fallbackTypeMap[zoteroType] ? Zotero.CSL.Item._fallbackTypeMap[zoteroType] : "article") == type;
|
|
}
|
|
|
|
/*
|
|
* Separates names into different types.
|
|
*/
|
|
Zotero.CSL.Item.prototype._separateNames = function() {
|
|
this._names = [];
|
|
|
|
var authorID = Zotero.CreatorTypes.getPrimaryIDForType(this.zoteroItem.itemTypeID);
|
|
|
|
var creators = this.zoteroItem.getCreators();
|
|
for each(var creator in creators) {
|
|
if(creator.creatorTypeID == authorID) {
|
|
var variable = "author";
|
|
} else {
|
|
var variable = Zotero.CreatorTypes.getName(creator.creatorTypeID);
|
|
}
|
|
|
|
var name = new Zotero.CSL.Item.Name(creator);
|
|
|
|
if(!this._names[variable]) {
|
|
this._names[variable] = [name];
|
|
} else {
|
|
this._names[variable].push(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generates an date object for a given variable (currently supported: issued
|
|
* and accessed)
|
|
*/
|
|
Zotero.CSL.Item.prototype._createDate = function(variable) {
|
|
// first, figure out what date variable to use.
|
|
if(variable == "issued") {
|
|
var date = this.zoteroItem.getField("date", false, true);
|
|
var sort = this.zoteroItem.getField("date", true, true);
|
|
} else if(variable == "accessed") {
|
|
var date = this.zoteroItem.getField("accessDate", false, true);
|
|
var sort = this.zoteroItem.getField("accessDate", true, true);
|
|
}
|
|
|
|
if(date) {
|
|
this._dates[variable] = new Zotero.CSL.Item.Date(date, sort);
|
|
} else {
|
|
this._dates[variable] = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Date class
|
|
*/
|
|
Zotero.CSL.Item.Date = function(date, sort) {
|
|
this.date = date;
|
|
this.sort = sort;
|
|
}
|
|
|
|
/*
|
|
* Should accept the following variables:
|
|
*
|
|
* year - returns a year (optionally, with attached B.C.)
|
|
* month - returns a month (numeric from 0, or, if numeric is not available, long)
|
|
* day - returns a day (numeric)
|
|
* sort - a date that can be used for sorting purposes
|
|
*/
|
|
Zotero.CSL.Item.Date.prototype.getDateVariable = function(variable) {
|
|
if(this.date) {
|
|
if(variable == "sort") {
|
|
return this.sort;
|
|
}
|
|
|
|
if(!this.dateArray) {
|
|
this.dateArray = Zotero.Date.strToDate(this.date);
|
|
}
|
|
|
|
if(this.dateArray[variable] !== undefined && this.dateArray[variable] !== false) {
|
|
return this.dateArray[variable];
|
|
} else if(variable == "month") {
|
|
if(this.dateArray.part) {
|
|
return this.dateArray.part;
|
|
}
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
* Name class
|
|
*/
|
|
Zotero.CSL.Item.Name = function(zoteroCreator) {
|
|
this._zoteroCreator = zoteroCreator;
|
|
}
|
|
|
|
/*
|
|
* Should accept the following variables:
|
|
*
|
|
* firstName - first name
|
|
* lastName - last name
|
|
*/
|
|
Zotero.CSL.Item.Name.prototype.getNameVariable = function(variable) {
|
|
return this._zoteroCreator.ref[variable] ? this._zoteroCreator.ref[variable] : "";
|
|
}
|
|
|
|
/*
|
|
* When an array of items are passed to create a new item set, each is wrapped
|
|
* in an item wrapper.
|
|
*/
|
|
Zotero.CSL.ItemSet = function(items, csl) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
this.csl = csl;
|
|
|
|
this.citation = csl._csl.citation;
|
|
this.bibliography = csl._csl.bibliography;
|
|
|
|
// collect options
|
|
this.options = new Object();
|
|
var options = this.citation.option.(@name.substr(0, 12) == "disambiguate")
|
|
+ this.bibliography.option.(@name == "subsequent-author-substitute");
|
|
for each(var option in options) {
|
|
this.options[option.@name.toString()] = option.@value.toString();
|
|
}
|
|
|
|
// check for disambiguate condition
|
|
for each(var thisIf in csl._csl..if) {
|
|
if(thisIf.@disambiguate.length()) {
|
|
this.options["disambiguate-condition"] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check for citation number
|
|
for each(var thisText in csl._csl..text) {
|
|
if(thisText.@variable == "citation-number") {
|
|
this.options["citation-number"] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set sortable
|
|
this.sortable = !!this.bibliography.sort.key.length();
|
|
|
|
this.items = [];
|
|
this.itemsById = {};
|
|
this.itemsByKey = {};
|
|
|
|
// add items
|
|
this.add(items);
|
|
|
|
// check which disambiguation options are enabled
|
|
this._citationChangingOptions = new Array();
|
|
this._disambiguate = false;
|
|
for(var option in this.options) {
|
|
if(option.substr(0, 12) == "disambiguate" && this.options[option]) {
|
|
this._citationChangingOptions.push(option);
|
|
this._disambiguate = true;
|
|
} else if(option == "citation-number" && this.options[option]) {
|
|
this._citationChangingOptions.push(option);
|
|
}
|
|
}
|
|
|
|
if(!items) {
|
|
return;
|
|
}
|
|
|
|
this.resort();
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets CSL.Item objects from an item set using their ids
|
|
*
|
|
* @param {Array} ids An array of ids
|
|
* @return {Array} items An array whose indexes correspond to those of ids, whose values are either
|
|
* the CSL.Item objects or false
|
|
**/
|
|
Zotero.CSL.ItemSet.prototype.getItemsByIds = function(ids) {
|
|
var items = [];
|
|
for each(var id in ids) {
|
|
if(this.itemsById[id] != undefined) {
|
|
items.push(this.itemsById[id]);
|
|
} else {
|
|
items.push(false);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
/**
|
|
* Gets CSL.Item objects from an item set using their keys
|
|
*
|
|
* @param {Array} keys An array of keys
|
|
* @return {Array} items An array whose indexes correspond to those of keys, whose values are either
|
|
* the CSL.Item objects or false
|
|
**/
|
|
Zotero.CSL.ItemSet.prototype.getItemsByKeys = function(keys) {
|
|
var items = [];
|
|
for each(var key in keys) {
|
|
if(this.itemsByKey[key] != undefined) {
|
|
items.push(this.itemsByKey[key]);
|
|
} else {
|
|
items.push(false);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
/*
|
|
* Adds items to the given item set; must be passed either CSL.Item
|
|
* objects or objects that may be wrapped as CSL.Item objects
|
|
*/
|
|
Zotero.CSL.ItemSet.prototype.add = function(items) {
|
|
var newItems = new Array();
|
|
|
|
for(var i in items) {
|
|
if(items[i] instanceof Zotero.CSL.Item) {
|
|
var newItem = items[i];
|
|
} else {
|
|
var newItem = new Zotero.CSL.Item(items[i]);
|
|
}
|
|
|
|
newItem.setProperty("index", this.items.length);
|
|
|
|
this.itemsById[newItem.id] = newItem;
|
|
this.itemsByKey[newItem.key] = newItem;
|
|
this.items.push(newItem);
|
|
newItems.push(newItem);
|
|
}
|
|
|
|
return newItems;
|
|
}
|
|
|
|
/*
|
|
* Removes items from the item set; must be passed either CSL.Item objects
|
|
* or item IDs
|
|
*/
|
|
Zotero.CSL.ItemSet.prototype.remove = function(items) {
|
|
for(var i in items) {
|
|
if(!items[i]) continue;
|
|
if(items[i] instanceof Zotero.CSL.Item) {
|
|
var item = items[i];
|
|
} else {
|
|
var item = this.itemsById[items[i]];
|
|
}
|
|
this.itemsById[item.id] = undefined;
|
|
this.itemsByKey[item.key] = undefined;
|
|
this.items.splice(this.items.indexOf(item), 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sorts the item set, also running postprocessing and returning items whose
|
|
* citations have changed
|
|
*/
|
|
Zotero.CSL.ItemSet.prototype.resort = function() {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
// sort
|
|
this.items = this.csl.cachedSort(this.items, this.bibliography);
|
|
|
|
// first loop through to collect disambiguation data by item, so we can
|
|
// see if any items have changed; also collect last names
|
|
var oldOptions = new Array();
|
|
for(var i in this._citationChangingOptions) {
|
|
oldOptions[i] = new Array();
|
|
for(var j in this.items) {
|
|
if(this.items[j] == undefined) continue;
|
|
oldOptions[i][j] = this.items[j].getProperty(this._citationChangingOptions[i]);
|
|
this.items[j].setProperty(this._citationChangingOptions[i], "");
|
|
}
|
|
}
|
|
|
|
var namesByItem = new Object();
|
|
for(var i=0; i<this.items.length; i++) {
|
|
var names = this.items[i].getNames("author");
|
|
if(!names) names = this.items[i].getNames("editor");
|
|
if(!names) names = this.items[i].getNames("translator");
|
|
if(!names) names = this.items[i].getNames("recipient");
|
|
if(!names) names = this.items[i].getNames("interviewer");
|
|
if(!names) names = this.items[i].getNames("collection-editor");
|
|
if(!names) continue;
|
|
namesByItem[i] = names;
|
|
}
|
|
|
|
// check where last names are the same but given names are different
|
|
if(this.options["disambiguate-add-givenname"]) {
|
|
var firstNamesByItem = new Object();
|
|
var allNames = new Object();
|
|
var nameType = new Object();
|
|
|
|
for(var i=0; i<this.items.length; i++) {
|
|
var names = namesByItem[i];
|
|
var firstNames = [];
|
|
for(var j=0; j<names.length; j++) {
|
|
// get firstName and lastName
|
|
var m = Zotero.CSL._firstNameRegexp.exec(names[j].getNameVariable("firstName"));
|
|
var firstName = m[0].toLowerCase();
|
|
firstNames.push(firstName);
|
|
if(!firstName) continue;
|
|
var lastName = names[j].getNameVariable("lastName");
|
|
|
|
// add last name
|
|
if(!allNames[lastName]) {
|
|
allNames[lastName] = [firstName];
|
|
} else if(allNames[lastName].indexOf(firstName) == -1) {
|
|
allNames[lastName].push(firstName);
|
|
}
|
|
}
|
|
|
|
firstNamesByItem[i] = firstNames;
|
|
}
|
|
|
|
// loop through last names
|
|
for(var i=0; i<this.items.length; i++) {
|
|
if(!namesByItem[i]) continue;
|
|
|
|
var nameFormat = new Array();
|
|
for(var j=0; j<namesByItem[i].length; j++) {
|
|
var lastName = namesByItem[i][j].getNameVariable("lastName");
|
|
if(nameType[lastName] === undefined) {
|
|
// determine how to format name
|
|
var theNames = allNames[lastName];
|
|
if(theNames && theNames.length > 1) {
|
|
// have two items with identical last names but different
|
|
// first names
|
|
nameType[lastName] = Zotero.CSL.NAME_USE_INITIAL;
|
|
|
|
// check initials to see if any match
|
|
var initials = new Object();
|
|
for(var k=0; k<theNames.length; k++) {
|
|
if(initials[theNames[k][0]]) {
|
|
nameType[lastName] = Zotero.CSL.NAME_USE_FULL;
|
|
break;
|
|
}
|
|
initials[theNames[k][0]] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
nameFormat[j] = nameType[lastName];
|
|
}
|
|
|
|
if(nameFormat.length) {
|
|
// if some names have special formatting, save
|
|
this.items[i].setProperty("disambiguate-add-givenname", nameFormat.join(","));
|
|
}
|
|
}
|
|
}
|
|
|
|
// loop through once to determine where items equal the previous item
|
|
if(this._disambiguate && this.items.length) {
|
|
var citationsEqual = this.csl.getEqualCitations(this.items, this.citation);
|
|
}
|
|
|
|
var allNames = {};
|
|
|
|
var lastItem = false;
|
|
var lastNames = false;
|
|
var lastYear = false;
|
|
var citationNumber = 1;
|
|
|
|
for(var i=0; i<this.items.length; i++) {
|
|
var item = this.items[i];
|
|
if(item == undefined) continue;
|
|
|
|
var year = item.getDate("issued");
|
|
if(year) year = year.getDateVariable("year");
|
|
var names = namesByItem[i];
|
|
var disambiguated = false;
|
|
|
|
if(this._disambiguate && i != 0 && citationsEqual[i] == true) {
|
|
// some options can only be applied if there are actual authors
|
|
if(names && lastNames && this.options["disambiguate-add-names"]) {
|
|
// try adding names to disambiguate
|
|
var oldAddNames = lastItem.getProperty("disambiguate-add-names");
|
|
|
|
// if a different number of names, disambiguation is
|
|
// easy, although we should still see if there is a
|
|
// smaller number of names that works
|
|
var numberOfNames = names.length;
|
|
if(numberOfNames > lastNames.length) {
|
|
numberOfNames = lastNames.length;
|
|
item.setProperty("disambiguate-add-names", numberOfNames+1);
|
|
|
|
// have to check old property
|
|
if(!oldAddNames || oldAddNames < numberOfNames) {
|
|
lastItem.setProperty("disambiguate-add-names", numberOfNames);
|
|
}
|
|
|
|
disambiguated = true;
|
|
} else if(numberOfNames != lastNames.length) {
|
|
item.setProperty("disambiguate-add-names", numberOfNames);
|
|
|
|
// have to check old property
|
|
if(!oldAddNames || oldAddNames < numberOfNames+1) {
|
|
lastItem.setProperty("disambiguate-add-names", numberOfNames+1);
|
|
}
|
|
|
|
disambiguated = true;
|
|
}
|
|
}
|
|
|
|
// now, loop through and see whether there's a
|
|
// dissimilarity before the end
|
|
var namesDiffer = false;
|
|
for(var j=0; j<numberOfNames; j++) {
|
|
namesDiffer = (names[j].getNameVariable("lastName") != lastNames[j].getNameVariable("lastName")
|
|
|| (firstNamesByItem && firstNamesByItem[i][j] != firstNamesByItem[i-1][j]));
|
|
if(this.options["disambiguate-add-names"] && namesDiffer) {
|
|
item.setProperty("disambiguate-add-names", j+1);
|
|
|
|
if(!oldAddNames || oldAddNames < j+1) {
|
|
lastItem.setProperty("disambiguate-add-names", j+1);
|
|
}
|
|
|
|
disambiguated = true;
|
|
}
|
|
|
|
if(namesDiffer) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add a year suffix, if the above didn't work
|
|
if(!disambiguated && year && !namesDiffer && this.options["disambiguate-add-year-suffix"]) {
|
|
var lastDisambiguate = lastItem.getProperty("disambiguate-add-year-suffix");
|
|
if(!lastDisambiguate) {
|
|
lastItem.setProperty("disambiguate-add-year-suffix", "a");
|
|
item.setProperty("disambiguate-add-year-suffix", "b");
|
|
} else {
|
|
var newDisambiguate = "";
|
|
if(lastDisambiguate.length > 1) {
|
|
newDisambiguate = oldLetter.substr(0, lastDisambiguate.length-1);
|
|
}
|
|
|
|
var charCode = lastDisambiguate.charCodeAt(lastDisambiguate.length-1);
|
|
if(charCode == 122) {
|
|
// item is z; add another letter
|
|
newDisambiguate += "a";
|
|
} else {
|
|
// next lowercase letter
|
|
newDisambiguate += String.fromCharCode(charCode+1);
|
|
}
|
|
|
|
item.setProperty("disambiguate-add-year-suffix", newDisambiguate);
|
|
}
|
|
|
|
disambiguated = true;
|
|
}
|
|
|
|
// use disambiguate condition if above didn't work
|
|
if(!disambiguated && this.options["disambiguate-condition"]) {
|
|
var oldCondition = lastItem.getProperty("disambiguate-condition");
|
|
lastItem.setProperty("disambiguate-condition", true);
|
|
item.setProperty("disambiguate-condition", true);
|
|
|
|
// if we cannot disambiguate with the conditional, revert
|
|
if(this.csl.compareCitations(lastItem, item) == 0) {
|
|
if(!oldCondition) {
|
|
lastItem.setProperty("disambiguate-condition", undefined);
|
|
}
|
|
item.setProperty("disambiguate-condition", undefined);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(this.options["subsequent-author-substitute"]
|
|
&& lastNames && names.length && lastNames.length == names.length) {
|
|
var namesDiffer = false;
|
|
for(var j=0; j<names.length; j++) {
|
|
namesDiffer = (names[j].getNameVariable("lastName") != lastNames[j].getNameVariable("lastName")
|
|
|| (names[j].getNameVariable("firstName") != lastNames[j].getNameVariable("firstName")));
|
|
if(namesDiffer) break;
|
|
}
|
|
|
|
if(!namesDiffer) item.setProperty("subsequent-author-substitute", true);
|
|
}
|
|
|
|
item.setProperty("citation-number", citationNumber++);
|
|
|
|
lastItem = item;
|
|
lastNames = names;
|
|
lastYear = year;
|
|
}
|
|
|
|
// find changed citations
|
|
var changedCitations = new Array();
|
|
for(var j in this.items) {
|
|
if(this.items[j] == undefined) continue;
|
|
for(var i in this._citationChangingOptions) {
|
|
if(this.items[j].getProperty(this._citationChangingOptions[i]) != oldOptions[i][j]) {
|
|
changedCitations.push(this.items[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return changedCitations;
|
|
}
|
|
|
|
/*
|
|
* Copies disambiguation settings (with the exception of disambiguate-add-year-suffix)
|
|
* from one item to another
|
|
*/
|
|
Zotero.CSL.ItemSet.prototype._copyDisambiguation = function(fromItem, toItem) {
|
|
for each(var option in ["disambiguate-add-givenname", "disambiguate-add-names",
|
|
"disambiguate-add-title"]) {
|
|
var value = fromItem.getProperty(option);
|
|
if(value) {
|
|
toItem.setProperty(option, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.CSL.FormattedString = function(context, format, delimiter, subsequent) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
this.context = context;
|
|
this.option = context ? context.option : new XMLList();
|
|
this.format = format;
|
|
this.delimiter = delimiter;
|
|
this.string = "";
|
|
this.closePunctuation = "";
|
|
this.closeFormatting = "";
|
|
this.useBritishStyleQuotes = false;
|
|
|
|
// insert tab iff second-field-align is on
|
|
this.insertTabAfterField = (!subsequent && this.option.(@name == "second-field-align").@value.toString());
|
|
this.insertTabBeforeField = false;
|
|
// append line before next
|
|
this.prependLine = false;
|
|
|
|
if(format == "RTF") {
|
|
this._openQuote = "\\uc0\\u8220 ";
|
|
this._closeQuote = "\\uc0\\u8221 ";
|
|
} else {
|
|
this._openQuote = "\u201c";
|
|
this._closeQuote = "\u201d";
|
|
}
|
|
}
|
|
|
|
Zotero.CSL.FormattedString._punctuation = "!.,?:";
|
|
|
|
/*
|
|
* attaches another formatted string to the end of the current one
|
|
*/
|
|
Zotero.CSL.FormattedString.prototype.concat = function(formattedString, element) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
if(!formattedString || !formattedString.string) {
|
|
return false;
|
|
}
|
|
|
|
if(formattedString.format != this.format) {
|
|
throw "CSL: cannot concatenate formatted strings: formats do not match";
|
|
}
|
|
|
|
var haveAppended = false;
|
|
var suffix = false;
|
|
if(formattedString.string !== "") {
|
|
// first, append the actual string without its suffix
|
|
if(element && element.@suffix.length()) {
|
|
// don't edit original element
|
|
element = element.copy();
|
|
// let us decide to add the suffix
|
|
suffix = element.@suffix.toString();
|
|
element.@suffix = [];
|
|
}
|
|
haveAppended = this.append(formattedString.string, element, false, true);
|
|
}
|
|
|
|
// if there's close punctuation to append, that also counts
|
|
if(formattedString.closePunctuation || formattedString.closeFormatting) {
|
|
haveAppended = true;
|
|
// add the new close punctuation
|
|
this.closeFormatting += formattedString.closeFormatting;
|
|
this.closePunctuation += formattedString.closePunctuation;
|
|
}
|
|
|
|
// append suffix, if we didn't before
|
|
if(haveAppended && suffix !== false) this.append(suffix, null, true);
|
|
|
|
return haveAppended;
|
|
}
|
|
|
|
Zotero.CSL.FormattedString._rtfEscapeFunction = function(aChar) {
|
|
return "{\\uc0\\u"+aChar.charCodeAt(0).toString()+"}"
|
|
}
|
|
|
|
/*
|
|
* appends a string (with format parameters) to the current one
|
|
*/
|
|
Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDelimit, dontEscape) {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl"; with({});
|
|
|
|
if(!string && string !== 0) return false;
|
|
|
|
if(typeof(string) != "string") {
|
|
string = string.toString();
|
|
}
|
|
|
|
// get prefix
|
|
var prefix = "";
|
|
if(element && element.@prefix.length()) {
|
|
var prefix = element.@prefix.toString();
|
|
}
|
|
|
|
// append tab before if necessary
|
|
if(!dontDelimit && this.insertTabBeforeField) {
|
|
// replace any space preceding tab
|
|
this.string = this.string.replace(/\s+$/, "");
|
|
|
|
if(this.format == "HTML") {
|
|
this.string += '</td><td style="padding-left:4pt;">';
|
|
} else if(this.format == "RTF") {
|
|
this.string += "\\tab ";
|
|
} else if(this.format == "Integration") {
|
|
this.string += "\t";
|
|
} else {
|
|
this.string += " ";
|
|
}
|
|
|
|
this.insertTabBeforeField = false;
|
|
if(prefix !== "") {
|
|
prefix = prefix.replace(/^\s+/, "");
|
|
} else {
|
|
string = string.replace(/^\s+/, "");
|
|
}
|
|
}
|
|
|
|
// append delimiter if necessary
|
|
if(this.delimiter && this.string && !dontDelimit) {
|
|
this.append(this.delimiter, null, true);
|
|
}
|
|
|
|
// append prefix before closing punctuation
|
|
if(prefix !== "") {
|
|
this.append(prefix, null, true);
|
|
}
|
|
|
|
var addBefore = "";
|
|
var addAfter = "";
|
|
|
|
// prepend line before if display="block"
|
|
if(element && (element["@display"] == "block" || this.prependLine)) {
|
|
if(this.format == "HTML") {
|
|
if(this.option.(@name == "hanging-indent").@value == "true") {
|
|
addBefore += '<div style="text-indent:0.5in;">'
|
|
} else {
|
|
addBefore += '<div>';
|
|
}
|
|
addAfter = '</div>';
|
|
} else {
|
|
if(this.format == "RTF") {
|
|
addBefore += "\r\n\\line ";
|
|
} else if(this.format == "Integration") {
|
|
addBefore += "\x0B";
|
|
} else {
|
|
addBefore += (Zotero.isWin ? "\r\n" : "\n");
|
|
}
|
|
this.prependLine = element["@display"] == "block";
|
|
}
|
|
}
|
|
|
|
// close quotes, etc. using punctuation
|
|
if(this.closePunctuation) {
|
|
if(Zotero.CSL.FormattedString._punctuation.indexOf(string[0]) != -1) {
|
|
this.string += string[0];
|
|
string = string.substr(1);
|
|
}
|
|
this.string += this.closePunctuation;
|
|
this.closePunctuation = "";
|
|
}
|
|
|
|
// clean up
|
|
if(string.length && string[0] == "." &&
|
|
Zotero.CSL.FormattedString._punctuation.indexOf(this.string[this.string.length-1]) != -1) {
|
|
// if string already ends in punctuation, preserve the existing stuff
|
|
// and don't add a period
|
|
string = string.substr(1);
|
|
} else if(this.string[this.string.length-1] == "(" && string[0] == " ") {
|
|
string = string.substr(1);
|
|
} else if(this.string[this.string.length-1] == " " && string[0] == ")") {
|
|
this.string = this.string.substr(0, this.string.length-1);
|
|
}
|
|
|
|
// close previous formatting
|
|
this.string += this.closeFormatting;
|
|
this.closeFormatting = "";
|
|
|
|
// handling of "text-transform" attribute (now obsolete)
|
|
if(element && element["@text-transform"].length() && !element["@text-case"].length()) {
|
|
var mapping = {"lowercase":"lowercase", "uppercase":"uppercase", "capitalize":"capitalize-first"};
|
|
element["@text-case"] = mapping[element["@text-transform"].toString()];
|
|
}
|
|
|
|
// handle text case
|
|
if(element) {
|
|
if(element["@text-case"].length()) {
|
|
if(element["@text-case"] == "lowercase") {
|
|
// all lowercase
|
|
string = string.toLowerCase();
|
|
} else if(element["@text-case"] == "uppercase") {
|
|
// all uppercase
|
|
string = string.toUpperCase();
|
|
} else if(element["@text-case"] == "sentence") {
|
|
// for now capitalizes only the first letter, the rest are lowercase
|
|
string = string[0].toUpperCase()+string.substr(1).toLowerCase();
|
|
} else if(element["@text-case"] == "capitalize-first") {
|
|
// capitalize first
|
|
string = string[0].toUpperCase()+string.substr(1);
|
|
} else if(element["@text-case"] == "capitalize-all") {
|
|
// capitalize first
|
|
var strings = string.split(" ");
|
|
for(var i=0; i<strings.length; i++) {
|
|
if(strings[i].length > 1) {
|
|
strings[i] = strings[i][0].toUpperCase()+strings[i].substr(1).toLowerCase();
|
|
} else if(strings[i].length == 1) {
|
|
strings[i] = strings[i].toUpperCase();
|
|
}
|
|
}
|
|
string = strings.join(" ");
|
|
} else if(element["@text-case"] == "title") {
|
|
string = Zotero.Text.titleCase(string);
|
|
}
|
|
}
|
|
|
|
// style attributes
|
|
if(this.format == "HTML") {
|
|
var style = "";
|
|
|
|
var cssAttributes = ["font-family", "font-style", "font-variant",
|
|
"font-weight", "vertical-align", "display",
|
|
"text-decoration" ];
|
|
for(var j in cssAttributes) {
|
|
var value = element["@"+cssAttributes[j]].toString();
|
|
if(value && value.indexOf('"') == -1) {
|
|
style += cssAttributes[j]+":"+value+";";
|
|
}
|
|
}
|
|
|
|
if(style) {
|
|
addBefore += '<span style="'+style+'">';
|
|
addAfter = '</span>'+addAfter;
|
|
}
|
|
} else {
|
|
if(this.format == "RTF" || this.format == "Integration") {
|
|
var rtfAttributes = {
|
|
"font-style":{"oblique":"i", "italic":"i"},
|
|
"font-variant":{"small-caps":"scaps"},
|
|
"font-weight":{"bold":"b"},
|
|
"text-decoration":{"underline":"ul"},
|
|
"vertical-align":{"sup":"super", "sub":"sub"}
|
|
}
|
|
|
|
for(var j in rtfAttributes) {
|
|
for(var k in rtfAttributes[j]) {
|
|
if(element["@"+j] == k) {
|
|
addBefore += "\\"+rtfAttributes[j][k]+" ";
|
|
addAfter = "\\"+rtfAttributes[j][k]+"0 "+addAfter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add quotes if necessary
|
|
if(element.@quotes == "true") {
|
|
this.string += this._openQuote;
|
|
|
|
if(this.useBritishStyleQuotes) {
|
|
string += this._closeQuote;
|
|
} else {
|
|
this.closePunctuation = this._closeQuote;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!dontEscape) {
|
|
if(this.format == "HTML") {
|
|
string = string.replace("&", "&", "g")
|
|
.replace("<", "<", "g")
|
|
.replace(">", ">", "g")
|
|
.replace(/(\r\n|\r|\n)/g, "<br />")
|
|
.replace(/[\x00-\x1F]/g, "");
|
|
} else if(this.format == "RTF" || this.format == "Integration") {
|
|
string = string.replace("\\", "\\\\", "g")
|
|
.replace(/(\r\n|\r|\n)/g, "\\line ");
|
|
if(string.substr(string.length-6) == "\\line ") {
|
|
string = string.substr(0, string.length-6);
|
|
addAfter = "\\line "+addAfter;
|
|
}
|
|
|
|
if(this.format == "RTF") {
|
|
string = string.replace(/[\x7F-\uFFFF]/g, Zotero.CSL.FormattedString._rtfEscapeFunction)
|
|
.replace("\t", "\\tab ", "g");
|
|
|
|
if(string.substr(string.length-5) == "\\tab ") {
|
|
string = string.substr(0, string.length-5);
|
|
addAfter = "\\tab "+addAfter;
|
|
}
|
|
}
|
|
} else {
|
|
string = string.replace(/(\r\n|\r|\n)/g, (Zotero.isWin ? "\r\n" : "\n"));
|
|
}
|
|
}
|
|
|
|
this.string += addBefore+string;
|
|
|
|
if(element && element.@suffix.length()) {
|
|
this.append(element.@suffix.toString(), null, true);
|
|
}
|
|
|
|
// save for second-field-align
|
|
if(!dontDelimit && this.insertTabAfterField) {
|
|
this.insertTabAfterField = false;
|
|
this.insertTabBeforeField = true;
|
|
}
|
|
|
|
this.closeFormatting = addAfter;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* gets the formatted string
|
|
*/
|
|
Zotero.CSL.FormattedString.prototype.get = function() {
|
|
return this.string+this.closeFormatting+this.closePunctuation;
|
|
}
|
|
|
|
/*
|
|
* creates a new formatted string with the same formatting parameters as this one
|
|
*/
|
|
Zotero.CSL.FormattedString.prototype.clone = function(delimiter) {
|
|
return new Zotero.CSL.FormattedString(this.context, this.format, delimiter, true);
|
|
}
|
|
|
|
/*
|
|
* Implementation of FormattedString for sort purposes.
|
|
*/
|
|
Zotero.CSL.SortString = function() {
|
|
default xml namespace = "http://purl.org/net/xbiblio/csl";
|
|
|
|
this.format = "Sort";
|
|
this.string = [];
|
|
}
|
|
|
|
Zotero.CSL.SortString.prototype.concat = function(newString) {
|
|
if(newString.string.length == 0) {
|
|
return;
|
|
} else if(newString.string.length == 1) {
|
|
this.string.push(newString.string[0]);
|
|
} else {
|
|
this.string.push(newString.string);
|
|
}
|
|
}
|
|
|
|
Zotero.CSL.SortString.prototype.append = function(newString) {
|
|
this.string.push(newString);
|
|
}
|
|
|
|
Zotero.CSL.SortString.prototype.compare = function(b, a) {
|
|
// by default, a is this string
|
|
if(a == undefined) {
|
|
a = this.string;
|
|
b = b.string;
|
|
}
|
|
|
|
var aIsString = typeof(a) != "object";
|
|
var bIsString = typeof(b) != "object";
|
|
if(aIsString && bIsString) {
|
|
if(a == b) {
|
|
return 0;
|
|
} else if(!isNaN(a % 1) && !isNaN(b % 1)) {
|
|
// both numeric
|
|
if(b > a) return -1;
|
|
return 1; // already know they're not equal
|
|
} else {
|
|
var cmp = Zotero.CSL.Global.collation.compareString(Zotero.CSL.Global.collation.kCollationCaseInSensitive, a, b);
|
|
if(cmp == 0) {
|
|
// for some reason collation service returned 0; the collation
|
|
// service sucks! they can't be equal!
|
|
if(b > a) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
return cmp;
|
|
}
|
|
} else if(aIsString && !bIsString) {
|
|
var cmp = this.compare(b[0], a);
|
|
if(cmp == 0) {
|
|
return -1; // a before b
|
|
}
|
|
return cmp;
|
|
} else if(bIsString && !aIsString) {
|
|
var cmp = this.compare(b, a[0]);
|
|
if(cmp == 0) {
|
|
return 1; // b before a
|
|
}
|
|
return cmp;
|
|
}
|
|
|
|
var maxLength = Math.min(b.length, a.length);
|
|
for(var i = 0; i < maxLength; i++) {
|
|
var cmp = this.compare(b[i], a[i]);
|
|
if(cmp != 0) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
if(b.length > a.length) {
|
|
return -1; // a before b
|
|
} else if(b.length < a.length) {
|
|
return 1; // b before a
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
Zotero.CSL.SortString.prototype.clone = function() {
|
|
return new Zotero.CSL.SortString();
|
|
} |