diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js index 75351072d..d125d7dc5 100644 --- a/chrome/content/zotero/xpcom/cite.js +++ b/chrome/content/zotero/xpcom/cite.js @@ -68,7 +68,15 @@ Zotero.Cite = new function() { function getStyle(cslID) { if(_lastStyle != cslID || Zotero.Prefs.get("cacheTranslatorData") == false) { // create a CSL instance - _lastCSL = new Zotero.CSL(_getCSL(cslID)); + var csl = _getCSL(cslID); + + // load CSL in compat mode if it is old-style + if(csl.indexOf("' : ''); + + if(this.class == "note" && isCitation) { + output += "
  • "+string+span+"
  • \r\n"; + } else { + output += "

    "+string+span+"

    \r\n"; + } + } else if(format == "RTF") { + if(this.class == "note" && isCitation) { + index++; + output += index+". "; + } + output += string+"\\\r\n\\\r\n"; + } else { + if(format == "Text" && this.class == "note" && isCitation) { + index++; + output += index+". "; + } + // attach \n on mac (since both \r and \n count as newlines for + // clipboard purposes) + output += string+(Zotero.isMac ? "\n\n" : "\r\n\r\n"); + } + } + + if(format == "HTML") { + if(this.class == "note" && isCitation) { + output += ''; + } else if(hangingIndent) { + output += ''; + } + } else if(format == "RTF") { + // drop last 6 characters of output (last two returns) + output = output.substr(0, output.length-6)+"}"; + } else { + // drop last 4 characters (last two returns) + output = output.substr(0, (Zotero.isMac ? output.length-2 : output.length-4)); + } + + return output; +} + +/* + * get a citation, given an item and bibCitElement + */ +Zotero.CSL.prototype._getCitation = function(item, context, format, position, locator, locatorType) { + Zotero.debug("CSL: generating citation for item"); + + var formattedString = new Zotero.CSL.FormattedString(this, format); + this._processElements(item, context.layout, formattedString, + context, position, locator, locatorType); + + return formattedString; +} + +/* + * gets a term, in singular or plural form + */ +Zotero.CSL.prototype._getTerm = function(term, plural, form) { + if(!form) { + form = "long"; + } + + if(!this._terms[form] || !this._terms[form][term]) { + Zotero.debug("CSL: WARNING: could not find term \""+term+'" with form "'+form+'"'); + return ""; + } + + if(typeof(this._terms[form][term]) == "object") { // singular and plural forms + // are available + if(plural) { + return this._terms[form][term][1]; + } else { + return this._terms[form][term][0]; + } + } + + return this._terms[form][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, position, variables) { + var children = element.children(); + if(!children.length()) return false; + var variableSucceeded = false; + + // Special routine for sorted names + if(formattedString.format == "Sort") { + for(var j=0; j= parseInt(etAlMin, 10)) { + maxCreators = parseInt(etAlUseFirst, 10); + useEtAl = true; + } + + // parse authors into strings + var authorStrings = []; + var firstName, lastName; + for(var i=0; i 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") || + (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(name == "label" && variables[j] != "author") { + newString.append(this._getTerm(variables[j], (maxCreators != 1), child["@form"].toString()), 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, position, locator, locatorType, ignore, isSingle) { + + if(!ignore) { + ignore = new Array(); + ignore[0] = new Array(); // for variables + ignore[1] = new Array(); // 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]*\?>/g, ""); } /* - * parses locale strings into Zotero.CSL.Global._defaultTerms + * parses locale strings into an array; */ - this.parseLocales = function(termXML, ignoreLang) { + function parseLocales(termXML, ignoreLang) { // return defaults if there are no terms if(!termXML.length()) { return (Zotero.CSL.Global._defaultTerms ? Zotero.CSL.Global._defaultTerms : {}); @@ -269,7 +874,7 @@ Zotero.CSL.Global = new function() { for each(var term in locale.term) { var name = term.@name.toString(); if(!name) { - throw("citations cannot be generated: no name defined on term in CSL"); + 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(); @@ -316,1189 +921,460 @@ Zotero.CSL.Global = new function() { return termArray; } - - /* - * pads a number or other string with a given string on the left - */ - this.lpad = function(string, pad, length) { - while(string.length < length) { - string = pad + string; - } - return string; - } } /* - * preprocess items, separating authors, editors, and translators arrays into - * separate properties - * - * must be called prior to generating citations or bibliography with a new set - * of items + * 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.prototype.preprocessItems = function(items) { - Zotero.debug("CSL: preprocessing items"); - - this._ignore = null; - - // get data necessary to generate citations before sorting - for(var i in items) { - var item = items[i]; - var dateModified = this._getField(item, "dateModified"); - - if(!item._csl || item._csl.dateModified != dateModified) { - // namespace everything in item._csl so there's no chance of overlap - item._csl = new Object(); - item._csl.dateModified = dateModified; - - // separate item into authors, editors, translators - var creators = this._separateItemCreators(item); - item._csl.authors = creators[0]; - item._csl.editors = creators[1]; - item._csl.translators = creators[2]; - - // parse date - item._csl.date = Zotero.CSL.prototype._processDate(this._getField(item, "date")); - } - // clear disambiguation and subsequent author substitute - if(item._csl.date && item._csl.date.disambiguation) item._csl.date.disambiguation = undefined; - if(item._csl.subsequentAuthorSubstitute) item._csl.subsequentAuthorSubstitute = undefined; - } - - // sort by sort order - if(this._bib.sortOrder) { - Zotero.debug("CSL: sorting items"); - var me = this; - items.sort(function(a, b) { - return me._compareItem(a, b); - }); - } - - // disambiguate items after preprocessing and sorting - var usedCitations = new Array(); - var lastAuthors; - - for(var i in items) { - var item = items[i]; - - // handle subsequent author substitutes - if(item._csl.authors.length && lastAuthors) { - var authorsAreSame = true; - for(var i=item._csl.authors.length-1; i>=0; i--) { - if(!lastAuthors[i] || - lastAuthors[i].firstName != item._csl.authors[i].firstName || - lastAuthors[i].lastName != item._csl.authors[i].lastName || - lastAuthors[i].creatorType != item._csl.authors[i].creatorType) { - authorsAreSame = false; - break; - } - } - if(authorsAreSame) item._csl.subsequentAuthorSubstitute = true; - } - lastAuthors = item._csl.authors; - - // handle (2006a) disambiguation for author-date styles - if(this.class == "author-date") { - var year = item._csl.date.year; - - if(authorsAreSame) { - if(usedCitations[year]) { - if(!usedCitations[year]._csl.date.disambiguation) { - usedCitations[year]._csl.date.disambiguation = "a"; - item._csl.date.disambiguation = "b"; - } else { - // get all but last character - var oldLetter = usedCitations[year]._csl.date.disambiguation; - if(oldLetter.length > 1) { - item._csl.date.disambiguation = oldLetter.substr(0, oldLetter.length-1); - } else { - item._csl.date.disambiguation = ""; - } - - var charCode = oldLetter.charCodeAt(oldLetter.length-1); - if(charCode == 122) { - // item is z; add another letter - item._csl.date.disambiguation += "za"; - } else { - // next lowercase letter - item._csl.date.disambiguation += String.fromCharCode(charCode+1); - } - } - } - } else { - usedCitations = new Array(); - } - - usedCitations[year] = item; - } - - // add numbers to each - item._csl.number = i; - } +Zotero.CSL.Item = function(item) { + this.zoteroItem = item; + this._dates = {}; + this._properties = {}; } -Zotero.CSL._locatorTerms = { - p:"page", - g:"paragraph", - l:"line" +/* + * Returns some identifier for the item. Used to create citations. In Zotero, + * this is the item ID + */ +Zotero.CSL.Item.prototype.getID = function() { + return this.zoteroItem.getID(); +} + +/* + * Gets an array of Item.Name objects for a variable. + */ +Zotero.CSL.Item.prototype.getNames = function(variable) { + 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) { + // load date variable if possible + if(this._dates[variable] == undefined) { + this._createDate(variable); + } + + if(this._dates[variable]) return this._dates[variable]; + return false; +} + +Zotero.CSL.Item._zoteroFieldMap = { + "title":"title", + "container-title":"publicationTitle", + "collection-title":["seriesTitle", "series"], + "publisher":"publisher", + "pages":"pages", + "volume":"volume", + "issue":"issue", + "number-of-volumes":"number-of-volumes", + "edition":"edition", + "genre":"type", + "abstract":"abstract", + "URL":"url", + "DOI":"doi" +} + +/* + * Gets a text object for a specific type. + */ +Zotero.CSL.Item.prototype.getText = function(variable, form) { + if(!Zotero.CSL.Item._zoteroFieldMap[variable]) return ""; + + if(variable == "title" && form == "short") { + var zoteroFields = ["shortTitle", "title"]; + } else { + var zoteroFields = Zotero.CSL.Item._zoteroFieldMap[variable]; + if(typeof zoteroFields == "string") zoteroFields = [zoteroFields]; + } + + for each(var zoteroField in zoteroFields) { + var value = this.zoteroItem.getField(zoteroField, false, true); + if(value != "") 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] ? this._properties[property] : ""); +} + +Zotero.CSL.Item._optionalTypeMap = { + journalArticle:"article-journal", + magazineArticle:"article-magazine", + newspaperArticle:"article-newspaper", + thesis:"thesis", + letter:"personal communication", + manuscript:"manuscript", + interview:"interview", + film:"motion picture", + artwork:"graphic", + webpage:"webpage", + report:"paper-conference", // ?? + 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:"paper-conference", + videoRecording:"motion picture", + tvBroadcast:"motion picture", + radioBroadcast:"motion picture", + podcast:"speech", // ?? + 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:"book", + letter:"article", + manuscript:"book", + interview:"book", + film:"book", + artwork:"book", + webpage:"article", + report:"book", + bill:"book", + case:"book", + hearing:"book", + patent:"book", + statute:"book", + email:"article", + map:"article", + blogPost:"article", + instantMessage:"article", + forumPost:"article", + audioRecording:"article", + presentation:"article", + videoRecording:"article", + tvBroadcast:"article", + radioBroadcast:"article", + podcast:"article", + computerProgram:"book" }; /* - * create a citation (in-text or footnote) - * accepts a citation object containing: - * - * citation.citationType - * 1 = first - * 2 = subsequent - * 3 = ibid - * 4 = ibid + pages - * - * citation.locators - * array of locators - * - * citation.locatorTypes - * array of types for given locators (see above) - * - * citation.items/citation.itemIDs - * array of items/itemIDs to be cited + * Determines whether this item is of a given type */ -Zotero.CSL.prototype.createCitation = function(citation, format) { - var string = new Zotero.CSL.FormattedString(this, format); - if(citation.citationType >= 3) { // indicates ibid - var term = this._getTerm("ibid"); - string.append(term[0].toUpperCase()+term.substr(1)); - - // if type == 4, attach pages - if(citation.citationType == 4) { - // locator data - var locator = citation.locators[0]; - - if(locator) { - var locatorType = Zotero.CSL._locatorTerms[citation.locatorTypes[0]]; - - // search for elements with the same serialization - var element = this._getFieldDefaults("locator"); - if(!element) { - element = { - name:"locator", - children:{name:"number"} - }; - } - - if(element) { - string.append("., "); - string.appendLocator(locatorType, locator, element); - } - } - } - } else { // indicates primary or subsequent - if(citation.itemIDs) citation.items = citation.itemIDs; - - var lasti = citation.items.length-1; - for(var i in citation.items) { - var locatorType = false; - var locator = false; - if(citation.locators) { - locatorType = Zotero.CSL._locatorTerms[citation.locatorTypes[i]]; - locator = citation.locators[i]; - } - - var position = (citation.citationType[i] == 1 ? "first" : "subsequent"); - - // allow either item objects or ids - if(typeof citation.items[i] == "object") { - item = citation.items[i]; - } else { - item = Zotero.Items.get(citation.items[i]); - } - - var citationString = this._getCitation(item, - position, - locatorType, locator, format, this._cit); - string.concat(citationString); - - if(this._cit.format && this._cit.format.delimiter && i != lasti) { - // add delimiter if one exists, and this isn't the last element - string.append(this._cit.format.delimiter); - } - } - } +Zotero.CSL.Item.prototype.isType = function(type) { + var zoteroType = Zotero.ItemTypes.getName(this.zoteroItem.getType()); - // add format - if(this._cit.format) { - // add citation prefix or suffix - if(this._cit.format.prefix) { - string.string = this._cit.format.prefix + string.string; - } - if(this._cit.format.suffix) { - string.append(this._cit.format.suffix); - } - } - - return string.get(); + return Zotero.CSL.Item._optionalTypeMap[zoteroType] == type + || Zotero.CSL.Item._fallbackTypeMap[zoteroType] == type; } /* - * create a bibliography - * (items is expected to be an array of items) + * Separates names into different types. */ -Zotero.CSL.prototype.createBibliography = function(items, format) { - // process this._items - var output = ""; +Zotero.CSL.Item.prototype._separateNames = function() { + this._names = []; - var index = 0; - if(format == "HTML") { - if(this.class == "note" && this._bib == this._cit) { - output += '
      \r\n'; - } else if(this._bib.hangingIndent) { - output += '
      \r\n'; - } - } else if(format == "RTF") { - output += "{\\rtf\\ansi{\\fonttbl\\f0\\froman Times New Roman;}{\\colortbl;\\red255\\green255\\blue255;}\\pard\\f0"; - if(this._bib.hangingIndent) { - output += "\\li720\\fi-720"; - } - output += "\r\n"; - } + var authorID = Zotero.CreatorTypes.getPrimaryIDForType(this.zoteroItem.getType()); - for(var i in items) { - var item = items[i]; - - var string = this._getCitation(item, "first", false, false, format, this._bib).get(); - if(!string) { - continue; - } - - // add format - if(this._bib.format) { - // add citation prefix or suffix - if(this._bib.format.prefix) { - string = this._bib.format.prefix + string ; - } - if(this._bib.format.suffix) { - string += this._bib.format.suffix; - } - } - - // add line feeds - if(format == "HTML") { - var coins = Zotero.OpenURL.createContextObject(item, "1.0"); - if(coins) { - string += ''; - } - - if(this.class == "note" && this._bib == this._cit) { - output += "
    1. "+string+"
    2. \r\n"; - } else { - output += "

      "+string+"

      \r\n"; - } - } else if(format == "RTF") { - if(this.class == "note" && this._bib == this._cit) { - index++; - output += index+". "; - } - output += string+"\\\r\n\\\r\n"; + var creators = this.zoteroItem.getCreators(); + for each(var creator in creators) { + if(creator.creatorTypeID == authorID) { + var variable = "author"; } else { - if(format == "Text" && this.class == "note" && this._bib == this._cit) { - index++; - output += index+". "; - } - // attach \n on mac (since both \r and \n count as newlines for - // clipboard purposes) - output += string+(Zotero.isMac ? "\n\n" : "\r\n\r\n"); + 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: published + * and accessed) + */ +Zotero.CSL.Item.prototype._createDate = function(variable) { + // first, figure out what date variable to use. + if(variable == "published") { + 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(format == "HTML") { - if(this.class == "note" && this._bib == this._cit) { - output += '
    '; - } else if(this._bib.hangingIndent) { - output += ''; - } - } else if(format == "RTF") { - // drop last 6 characters of output (last two returns) - output = output.substr(0, output.length-6)+"}"; + if(date) { + this._dates[variable] = new Zotero.CSL.Item.Date(date, sort); } else { - // drop last 4 characters (last two returns) - output = output.substr(0, (Zotero.isMac ? output.length-2 : output.length-4)); - } - - return output; -} - -/* - * parses attributes and children for a CSL field - */ -Zotero.CSL.prototype._parseFieldAttrChildren = function(element, desc, ignoreChildren) { - if(!desc) { - var desc = new Object(); - } - - // copy attributes - var attributes = element.attributes(); - for each(var attribute in attributes) { - desc[attribute.name()] = attribute.toString(); - } - - var children = element.children(); - if(!ignoreChildren) { - if(children.length()) { - // parse children - - if(children.length() > element.substitute.length()) { - // if there are non-substitute children, clear the current children - // array - desc.children = new Array(); - } - - // add children to children array - for each(var child in children) { - if(child.namespace() == Zotero.CSL.Global.ns) { // ignore elements in other - // namespaces - // parse recursively - var name = child.localName(); - if(name == "substitute") { - // place substitutes in their own key, so that they're - // overridden separately - if(child.choose.length) { // choose - desc.substitute = new Array(); - - var chooseChildren = child.choose.children(); - for each(var choose in chooseChildren) { - if(choose.namespace() == Zotero.CSL.Global.ns) { - var option = new Object(); - option.name = choose.localName(); - this._parseFieldAttrChildren(choose, option); - desc.substitute.push(option); - } - } - } else { // don't choose - desc.substitute = child.text().toString(); - } - } else { - var childDesc = this._parseFieldAttrChildren(child); - childDesc.name = name; - desc.children.push(childDesc); - } - } - } - } - } - - return desc; -} - -/* - * parses a list of fields into a defaults associative array - */ -Zotero.CSL.prototype._parseFieldDefaults = function(ref) { - for each(var element in ref.children()) { - if(element.namespace() == Zotero.CSL.Global.ns) { // ignore elements in other namespaces - var name = element.localName(); - Zotero.debug("CSL: parsing field defaults for "+name); - var fieldDesc = this._parseFieldAttrChildren(element); - - if(this._defaults[name]) { // inherit from existing defaults - this._defaults[name] = this._merge(this._defaults[name], - fieldDesc); - } else { - this._defaults[name] = fieldDesc; - } - } + this._dates[variable] = false; } } /* - * parses a list of fields into an array of objects + * Date class */ -Zotero.CSL.prototype._parseFields = function(ref, position, type, bibCitElement, inheritFormat) { - var typeDesc = new Array(); - for each(var element in ref) { - if(element.namespace() == Zotero.CSL.Global.ns) { // ignore elements in other namespaces - var itemDesc = new Object(); - itemDesc.name = element.localName(); - - // add defaults, but only if we're parsing as a reference type - if(type != undefined) { - var fieldDefaults = this._getFieldDefaults(itemDesc.name); - itemDesc = this._merge(fieldDefaults, itemDesc); - if(bibCitElement && inheritFormat) { - itemDesc = this._merge(bibCitElement.inheritFormat, itemDesc); - } - } - - // parse group children - if(itemDesc.name == "group" || itemDesc.name == "conditional" || - itemDesc.name == "if" || itemDesc.name == "else-if" || - itemDesc.name == "else") { - // parse attributes on this field, but ignore children - this._parseFieldAttrChildren(element, itemDesc, true); - - var children = element.children(); - if(children.length()) { - itemDesc.children = this._parseFields(children, position, type, bibCitElement); - } - } else { - // parse attributes on this field - this._parseFieldAttrChildren(element, itemDesc); - } - - if(type != undefined) { - // create serialized representation - itemDesc._serialized = this._serializeElement(itemDesc.name, itemDesc); - // add to serialization for type - if(bibCitElement && bibCitElement._serializations) { - bibCitElement._serializations[position][type][itemDesc._serialized] = itemDesc; - } - } - - typeDesc.push(itemDesc); - } - } - return typeDesc; +Zotero.CSL.Item.Date = function(date, sort) { + this.date = date; + this.sort = sort; } /* - * parses an et al field + * Should accept the following variables: + * + * year - returns a year (optionally, with attached B.C.) + * month - returns a month (numeric, or, if numeric is not available, long) + * day - returns a day (numeric) + * sort - a date that can be used for sorting purposes */ -Zotero.CSL.prototype._parseEtAl = function(etAl, bibCitElement) { - if(etAl.length()) { - bibCitElement.etAl = new Object(); - - if(etAl.length() > 1) { - // separate first and subsequent et als - for each(var etAlElement in etAl) { - if(etAlElement.@position == "subsequent") { - bibCitElement.subsequentEtAl = new Object(); - bibCitElement.subsequentEtAl.minCreators = parseInt(etAlElement['@min-authors'], 10); - bibCitElement.subsequentEtAl.useFirst = parseInt(etAlElement['@use-first'], 10); - } else { - var parseElement = etAlElement; - } - } - } else { - var parseElement = etAl; +Zotero.CSL.Item.Date.prototype.getDateVariable = function(variable) { + if(this.date) { + if(variable == "sort") { + return this.sort; } - bibCitElement.etAl.minCreators = parseInt(parseElement['@min-authors'], 10); - bibCitElement.etAl.useFirst = parseInt(parseElement['@use-first'], 10); - } else if(this._defaults["et-al"]) { - bibCitElement.etAl = new Object(); - - bibCitElement.etAl.minCreators = parseInt(this._defaults["et-al"]['min-authors'], 10); - bibCitElement.etAl.useFirst = parseInt(this._defaults["et-al"]['use-first'], 10); - } -} - -/* - * parses cs-format attributes into just a prefix and a suffix; accepts an - * optional array of cs-format - */ -Zotero.CSL.prototype._parseBibliographyOptions = function() { - if(!this._csl.bibliography.length()) { - return; - } - - var bibliography = this._csl.bibliography; - this._bib = new Object(); - - // global prefix and suffix format information - this._bib.inheritFormat = new Array(); - for each(var attribute in bibliography.layout.item.attributes()) { - this._bib.inheritFormat[attribute.name()] = attribute.toString(); - } - - // sections (TODO) - this._bib.sections = [{groupBy:"default", - heading:bibliography.layout.heading.text["@term-name"].toString()}]; - for each(var section in bibliography.layout.section) { - this._bib.sections.push([{groupBy:section["@group-by"].toString(), - heading:section.heading.text["@term-name"].toString()}]); - } - - // subsequent author substitute - // replaces subsequent occurances of an author with a given string - if(bibliography['@subsequent-author-substitute']) { - this._bib.subsequentAuthorSubstitute = bibliography['@subsequent-author-substitute'].toString(); - } - - // hanging indent - if(bibliography['@hanging-indent']) { - this._bib.hangingIndent = true; - } - - // sort order - var algorithm = bibliography.sort.@algorithm.toString(); - if(algorithm) { - // for classes, use the sort order that - if(algorithm == "author-date") { - this._bib.sortOrder = [this._getFieldDefaults("author"), - this._getFieldDefaults("date"), - this._getFieldDefaults("titles")]; - this._bib.sortOrder[0].name = "author"; - this._bib.sortOrder[0]["name-as-sort-order"] = "all"; - this._bib.sortOrder[1].name = "date"; - this._bib.sortOrder[2].name = "titles"; - } else if(algorithm == "label") { - this._bib.sortOrder = [this._getFieldDefaults("label")]; - this._bib.sortOrder[0].name = "label"; - } else if(algorithm == "cited") { - this._bib.sortOrder = [this._getFieldDefaults("cited")]; - this._bib.sortOrder[0].name = "cited"; - } - } else { - this._bib.sortOrder = this._parseFields(bibliography.sort.children(), "first", false, this._bib); - } - - // parse et al - this._parseEtAl(bibliography["et-al"], this._bib); - - // parse types - this._parseTypes(this._csl.bibliography.layout.item, this._bib); -} - -/* - * parses cs-format attributes into just a prefix and a suffix; accepts an - * optional array of cs-format - */ -Zotero.CSL.prototype._parseCitationOptions = function() { - var citation = this._csl.citation; - this._cit = new Object(); - - // parse et al - this._parseEtAl(citation["et-al"], this._cit); - - // global format information - this._cit.format = new Array(); - for each(var attribute in citation.attributes()) { - this._cit.format[attribute.name()] = attribute.toString(); - } - - // parse types - this._parseTypes(this._csl.citation.layout.item, this._cit); -} - -/* - * determine available reference types and add their XML objects to the tree - * (they will be parsed on the fly when necessary; see _parseReferenceType) - */ -Zotero.CSL.prototype._parseTypes = function(itemElements, bibCitElement) { - Zotero.debug("CSL: parsing item elements"); - - bibCitElement._types = new Object(); - bibCitElement._serializations = new Object(); - - // find the type item without position="subsequent" - for each(var itemElement in itemElements) { - var position = itemElement.@position.toString(); - if(position) { - // handle ibids - if(position == "subsequent" && - itemElement.@ibid.toString() == "true") { - this.ibid = true; - } - } else { - position = "first"; + if(!this.dateArray) { + this.dateArray = Zotero.Date.strToDate(this.date); } - if(!bibCitElement._types[position]) { - bibCitElement._types[position] = new Object(); - bibCitElement._serializations[position] = new Object(); - } - - // create an associative array of available types - if(itemElement.choose.length()) { - for each(var type in itemElement.choose.type) { - bibCitElement._types[position][type.@name] = type; - bibCitElement._serializations[position][type.@name] = new Object(); - } - } else { - // if there's only one type, bind it to index 0 - bibCitElement._types[position][0] = itemElement; - bibCitElement._serializations[position][0] = new Object(); - } - } -} - -/* - * convert reference types to native structures for speed - */ -Zotero.CSL.prototype._getTypeObject = function(position, reftype, bibCitElement) { - if(!bibCitElement._types[position][reftype]) { - // no type available - return false; - } - - // parse type if necessary - if(typeof(bibCitElement._types[position][reftype]) == "xml") { - Zotero.debug("CSL: parsing XML for "+reftype); - bibCitElement._types[position][reftype] = this._parseFields( - bibCitElement._types[position][reftype].children(), - position, reftype, bibCitElement, true); - } - - Zotero.debug("CSL: got object for "+reftype); - return bibCitElement._types[position][reftype]; -} - -/* - * merges two elements, letting the second override the first - */ -Zotero.CSL.prototype._merge = function(element1, element2) { - var mergedElement = new Object(); - for(var i in element1) { - mergedElement[i] = element1[i]; - } - for(var i in element2) { - mergedElement[i] = element2[i]; - } - return mergedElement; -} - -/* - * gets defaults for a specific element; handles various inheritance rules - * (contributor, locator) - */ -Zotero.CSL.prototype._getFieldDefaults = function(elementName) { - // first, see if there are specific defaults - if(this._defaults[elementName]) { - if(Zotero.CSL.Global.inherit[elementName]) { - var inheritedDefaults = this._getFieldDefaults(Zotero.CSL.Global.inherit[elementName]); - for(var i in inheritedDefaults) { // will only be called if there - // is merging necessary - return this._merge(inheritedDefaults, this._defaults[elementName]); + if(this.dateArray[variable]) { + return this.dateArray[variable]; + } else if(variable == "month") { + if(this.dateArray.part) { + return this.dateArray.part; } } - return this._defaults[elementName]; - } - // next, try to get defaults from the item from which this item inherits - if(Zotero.CSL.Global.inherit[elementName]) { - return this._getFieldDefaults(Zotero.CSL.Global.inherit[elementName]); - } - // finally, return an empty object - return {}; -} - -/* - * gets a term, in singular or plural form - */ -Zotero.CSL.prototype._getTerm = function(term, plural, form) { - if(!form) { - form = "long"; - } - if(!this._terms[form][term]) { - return ""; } - if(typeof(this._terms[form][term]) == "object") { // singular and plural forms - // are available - if(plural) { - return this._terms[form][term][1]; - } else { - return this._terms[form][term][0]; - } - } - - return this._terms[form][term]; -} - - -/* - * serializes an element into a string suitable to prevent substitutes from - * recurring in the same style - */ -Zotero.CSL.prototype._serializeElement = function(name, element) { - var string = name; - if(element.relation) { - string += " relation:"+element.relation; - } - if(element.role) { - string += " role:"+element.role; - } - return string; + return ""; } /* - * handles sorting of items + * Name class */ -Zotero.CSL.prototype._compareItem = function(a, b, opt) { +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[variable] ? this._zoteroCreator[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(unwrappedItems, csl) { var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"] .getService(Components.interfaces.nsILocaleService); var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"] .getService(Components.interfaces.nsICollationFactory); - var collation = collationFactory.CreateCollation(localeService.getApplicationLocale()); + this._collation = collationFactory.CreateCollation(localeService.getApplicationLocale()); - for(var i in this._bib.sortOrder) { - var sortElement = this._bib.sortOrder[i]; + this.csl = csl; + + this.postprocess = false; + this.bib = csl._csl.bibliography; + if(this.bib.option.(@name == "disambiguate").length() + || this.bib.option.(@name == "subsequent-author-substitute").length()) { + this.postprocess = true; + } + + this.items = []; + this.itemsById = {}; + + if(!unwrappedItems) { + return; + } + + // add data (authors, editors, translators, etc.) + for(var i in unwrappedItems) { + var newItem = new Zotero.CSL.Item(unwrappedItems[i]); + this.items.push(newItem); + this.itemsById[newItem.getID()] = newItem; + } + + if(this.bib.option.(@name == "sort-algorithm").length() + && this.bib.option.(@name == "sort-algorithm").@value != "cited") { + this.resort(); + } + + // run postprocessing (subsequent author substitute and disambiguation) + if(this.postprocess) { + var lastItem = false; + var lastNames = false; + var lastYear = false; - if(sortElement.name == "date") { - var aValue = a.getField("date", true); - var bValue = b.getField("date", true); + for(var i in this.items) { + var item = this.items[i]; - if(bValue == "" && aValue != "") { - return -1; - } else if(aValue == "" && bValue != "") { - return 1; - } else if(bValue > aValue) { - return -1; - } else if(bValue < aValue) { - return 1; + var year = item.getDate("published"); + if(year) year = year.getDateVariable("year"); + var names = item.getNames("author"); + + if(names && lastNames && !this._compareNames(names, lastNames)) { + if(year && year == lastYear) { + var oldDisambiguate = lastItem.getProperty("disambiguate"); + if(!oldDisambiguate) { + lastItem.setProperty("disambiguate", "a"); + item.setProperty("disambiguate", "b"); + } else { + var newDisambiguate = ""; + if(oldDisambiguate.length > 1) { + newDisambiguate = oldLetter.substr(0, oldDisambiguate.length-1); + } + + var charCode = oldDisambiguate.charCodeAt(oldDisambiguate.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", newDisambiguate); + } + } + + item.setProperty("subsequent-author-substitute", "1"); } - } else { - var formattedStringA = new Zotero.CSL.FormattedString(this, "compare"); - var formattedStringB = new Zotero.CSL.FormattedString(this, "compare"); - //Zotero.debug('comparing '+sortElement.name+' on "'+a.getField("title")+'" and "'+b.getField("title")+'"'); + item.setProperty("number", i+1); - this._getFieldValue(sortElement.name, sortElement, a, - formattedStringA, this._bib); - this._getFieldValue(sortElement.name, sortElement, b, - formattedStringB, this._bib); - - var aValue = formattedStringA.get().toLowerCase(); - var bValue = formattedStringB.get().toLowerCase(); - //Zotero.debug(aValue+" vs "+bValue); - - var cmp = collation.compareString(0, aValue, bValue); - if(cmp != 0) { - return cmp; - } + lastItem = item; + lastNames = names; + lastYear = year; } } +} +/* + * Sorts the item set, running postprocessing afterwards + */ +Zotero.CSL.ItemSet.prototype.resort = function() { + var me = this; + + this.items = this.items.sort(function(a, b) { + return me._compareItem(a, b); + }); +} + +/* + * Gets CSL.Item objects from an item set using their IDs + */ +Zotero.CSL.ItemSet.prototype.getItemsByIds = function(ids) { + var items = []; + for each(var id in ids) { + if(this.itemsById[id]) { + items.push(this.itemsById[id]); + } + } + return items; +} + +/* + * 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.ItemSet.prototype._compareItem = function(a, b) { + var sortA = []; + var sortB = []; + + // author + if(this.bib.option.(@name == "sort-algorithm").@value == "author-date") { + var sortString = new Zotero.CSL.SortString(); + this.csl._processElements(a, this.csl._csl.macro.(@name == "author"), sortString); + sortA.push(sortString.get().toLowerCase()); + var date = a.getDate("published"); + if(date) sortA.push(date.getDateVariable("sort")); + + sortString = new Zotero.CSL.SortString(); + this.csl._processElements(b, this.csl._csl.macro.(@name == "author"), sortString); + sortB.push(sortString.get().toLowerCase()); + var date = b.getDate("published"); + if(date) sortB.push(date.getDateVariable("sort")); + } + + var compareNum = Math.min(sortA.length, sortB.length); + for(i=0; i= etAl.minCreators) { - maxCreators = etAl.useFirst; - useEtAl = true; - } - - // parse authors into strings - var authorStrings = []; - var firstName, lastName; - for(var i=0; i 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"]) { - 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 - Zotero.debug(child["delimiter-precedes-last"]); - if((authorStrings.length == 2 && child["delimiter-precedes-last"] != "always") || - (authorStrings.length > 2 && child["delimiter-precedes-last"] == "never")) { - var lastString = authorStrings.pop(); - authorStrings[authorStrings.length-1] = authorStrings[authorStrings.length-1]+" "+lastString; - } - } - string = authorStrings.join(joinString); - } else if(child.name == "label") { - string = this._getTerm(type, (maxCreators != 1), child["form"]); - } - - - // add string to data - if(string) { - data.append(string, child); - } + if(aString != bString) { + return this._collation.compareString(0, aString, bString);; } - - // add to the data - return data; + return 0; } -/* - * get a citation, given an item and bibCitElement - */ -Zotero.CSL.prototype._getCitation = function(item, position, locatorType, locator, format, bibCitElement) { - Zotero.debug("CSL: generating citation for item "+item.getID()); - - // use true position if possible, otherwise "first" - var typePosition = (bibCitElement._types[position] ? position : "first"); - - // determine mapping - if(bibCitElement._types[typePosition][0]) { - // only one element - var typeName = 0; - var type = this._getTypeObject(typePosition, typeName, bibCitElement); - } else { - var typeNames = this._getTypeFromItem(item); - for each(var typeName in typeNames) { - var type = this._getTypeObject(typePosition, typeName, bibCitElement); - if(type) { - break; - } - } - } - - Zotero.debug("CSL: using CSL type "+typeName); - - // remove previous ignore entries from list - this._ignore = new Array(); - - var formattedString = new Zotero.CSL.FormattedString(this, format); - for(var j in type) { - this._getFieldValue(type[j].name, type[j], item, formattedString, - bibCitElement, position, locatorType, locator, - typeName); - } - - return formattedString; -} - -/* - * processes an element from a (pre-processed) item into text - */ -Zotero.CSL.prototype._getFieldValue = function(name, element, item, formattedString, - bibCitElement, position, - locatorType, locator, typeName) { - - - var dataAppended = false; - var itemID = item.getID(); - - if(element._serialized && this._ignore && this._ignore[element._serialized]) { - return false; - } - - if(name == "author") { - if(item._csl.subsequentAuthorSubstitute && bibCitElement.subsequentAuthorSubstitute) { - // handle subsequent author substitute behavior - dataAppended = formattedString.append(bibCitElement.subsequentAuthorSubstitute, element); - } else { - var newString = this._processCreators(name, element, item._csl.authors, formattedString.format, bibCitElement, position); - if(newString) dataAppended = formattedString.concat(newString, element); - } - } else if(name == "editor") { - dataAppended = formattedString.concat(this._processCreators(name, element, item._csl.editors, formattedString.format, bibCitElement, position), element); - } else if(name == "translator") { - dataAppended = formattedString.concat(this._processCreators(name, element, item._csl.translators, formattedString.format, bibCitElement, position), element); - } else if(name == "titles") { - var data = new Zotero.CSL.FormattedString(this, formattedString.format); - - for(var i in element.children) { - var child = element.children[i]; - var string = null; - - if(child.name == "title") { // for now, we only care about the - // "title" sub-element - if(!element.relation) { - // preferentially use shortTitle if flagged - if(element.form && element.form == "short") { - string = this._getField(item, "shortTitle"); - } - if(!string) { - string = this._getField(item, "title"); - } - } else if(element.relation == "container") { - string = this._getField(item, "publicationTitle"); - } else if(element.relation == "collection") { - string = this._getField(item, "seriesTitle"); - if(!string) string = this._getField(item, "series"); - } else if(element.relation == "event") { - string = this._getField(item, "conferenceName"); - } - - // if comparing, drop "a" or "the" from title - if(formattedString.format == "compare" && string.length > 1) { - if(string.substr(0, 2).toLowerCase() == "a ") { - string = string.substr(2); - } else if(string.length > 3 && string.substr(0, 4).toLowerCase() == "the ") { - string = string.substr(4); - } - } - } - - if(string) { - data.append(string, child); - } - } - - dataAppended = formattedString.concat(data, element); - } else if(name == "date") { - dataAppended = formattedString.appendDate(item._csl.date, element); - } else if(name == "publisher") { - var data = new Zotero.CSL.FormattedString(this, formattedString.format); - - for(var i in element.children) { - var child = element.children[i]; - var string = ""; - - if(child.name == "place") { - string = this._getField(item, "place"); - } else if(child.name == "name") { - string = this._getField(item, "publisher"); - } - - if(string) { - data.append(string, child); - } - } - - dataAppended = formattedString.concat(data, element); - } else if(name == "access") { - var data = new Zotero.CSL.FormattedString(this, formattedString.format); - var text = null; - var save = false; - - for(var i in element.children) { - text = null; - var child = element.children[i]; - - if(child.name == "url") { - text = this._getField(item, "url"); - } else if(child.name == "date") { - var field = this._getField(item, "accessDate"); - if(field) { - data.appendDate(this._processDate(field), child); - save = true; - } - } else if(child.name == "physicalLocation") { - text = this._getField(item, "archiveLocation"); - } else if(child.name == "text") { - text = this._getTerm(child["term-name"], false, child["form"]); - } - - if(text) { - data.append(text, child); - if(child.name != "text") { - // only save if there's non-text data - save = true; - } - } - } - - if(save) { - dataAppended = formattedString.concat(data, element); - } - } else if(name == "volume" || name == "issue") { - var data = new Zotero.CSL.FormattedString(this, formattedString.format); - - var field = this._getField(item, name); - if(field) { - dataAppended = formattedString.appendLocator(name, field, element); - } - } else if(name == "pages") { - if(locatorType == "page") { - var field = locator; - } else if(typeName != "book") { - var field = this._getField(item, "pages"); - } - - if(field) { - dataAppended = formattedString.appendLocator("page", field, element); - } - } else if(name == "locator") { - if(locator) { - dataAppended = formattedString.appendLocator(locatorType, locator, element); - } - } else if(name == "edition") { - dataAppended = formattedString.append(this._getField(item, "edition"), element); - } else if(name == "genre") { - var data = this._getField(item, "type"); - if(!data) { - data = this._getField(item, "thesisType"); - } - dataAppended = formattedString.append(data, element); - } else if(name == "group") { - var data = new Zotero.CSL.FormattedString(this, formattedString.format, element["delimiter"]); - - for(var i in element.children) { - // get data for each child element - var child = element.children[i]; - - this._getFieldValue(child.name, child, item, data, bibCitElement, - position, typeName); - } - - dataAppended = formattedString.concat(data, element); - } else if(name == "conditional") { - var status = false; - for(var i in element.children) { - var condition = element.children[i]; - - if(condition.name == "if" || condition.name == "else-if") { - // evaluate condition for if/else if - if(condition.type) { - var typeNames = this._getTypeFromItem(item); - for each(var typeName in typeNames) { - if(typeName == condition.type) { - status = true; - break; - } - } - } else if(condition.field) { - var testString = new Zotero.CSL.FormattedString(this, "Text"); - status = this._getFieldValue(condition.field, this._getFieldDefaults(condition.field), item, - testString, bibCitElement); - } - } else if(condition.name == "else") { - status = true; - } - - if(status) { - var data = new Zotero.CSL.FormattedString(this, formattedString.format, element["delimiter"]); - for(var j in condition.children) { - // get data for each child element - var child = condition.children[j]; - - this._getFieldValue(child.name, child, item, data, bibCitElement, - position, typeName); - } - dataAppended = formattedString.concat(data, condition); - break; - } - } - } else if(name == "text") { - dataAppended = formattedString.append(this._getTerm(element["term-name"], false, element["form"]), element); - } else if(name == "isbn" || name == "doi") { - var field = this._getField(item, name.toUpperCase()); - if(field) { - dataAppended = formattedString.appendLocator(null, field, element); - } - } else if(name == "number") { - dataAppended = formattedString.append(this._csl.number, element); - } - - // if no change and there's a substitute, try it - if(dataAppended) { - return true; - } else if (element.substitute) { - // try each substitute element until one returns something - for(var i in element.substitute) { - var substituteElement = element.substitute[i]; - var serialization = this._serializeElement(substituteElement.name, - substituteElement); - - var inheritElement; - if(Zotero.CSL.Global.inherit[substituteElement.name] && Zotero.CSL.Global.inherit[name] - && Zotero.CSL.Global.inherit[substituteElement.name] == Zotero.CSL.Global.inherit[name]) { - // if both substituteElement and the parent element inherit from - // the same base element, apply styles here - inheritElement = element; - } else { - // search for elements with the same serialization - if(typeName != undefined && bibCitElement._serializations[position] - && bibCitElement._serializations[position][typeName] - && bibCitElement._serializations[position][typeName][serialization]) { - inheritElement = bibCitElement._serializations[position][typeName][serialization]; - } else { - // otherwise, use defaults - inheritElement = this._getFieldDefaults(substituteElement.name); - } - } - - // merge inheritElement and element - substituteElement = this._merge(inheritElement, substituteElement); - // regardless of inheritance pathway, make sure substitute inherits - // general prefix and suffix from the element it's substituting for - substituteElement.prefix = element.prefix; - substituteElement.suffix = element.suffix; - substituteElement.form = element.form; - // clear substitute element off of the element we're substituting - substituteElement.substitute = undefined; - - // get field value - dataAppended = this._getFieldValue(substituteElement.name, - substituteElement, item, formattedString, - bibCitElement, position, typeName); - - // ignore elements with the same serialization - if(this._ignore) { // array might not exist if doing disambiguation - this._ignore[serialization] = true; - } - - // return field value, if there is one; otherwise, keep processing - // the data - if(dataAppended) { - return true; - } - } - } - - return false; -} - - Zotero.CSL.FormattedString = function(CSL, format, delimiter) { this.CSL = CSL; this.format = format; @@ -1516,7 +1392,7 @@ Zotero.CSL.FormattedString = function(CSL, format, delimiter) { } } -Zotero.CSL.FormattedString._punctuation = ["!", ".", ",", "?"]; +Zotero.CSL.FormattedString._punctuation = "!.,?"; /* * attaches another formatted string to the end of the current one @@ -1566,8 +1442,8 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli } // append prefix before closing punctuation - if(element && element.prefix && this.format != "compare") { - this.append(element.prefix, null, true); + if(element && element.@prefix.length()) { + this.append(element.@prefix.toString(), null, true); } // close quotes, etc. using punctuation @@ -1582,14 +1458,14 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli // handle text transformation if(element) { - if(element["text-transform"]) { - if(element["text-transform"] == "lowercase") { + if(element["@text-transform"].length()) { + if(element["@text-transform"] == "lowercase") { // all lowercase string = string.toLowerCase(); - } else if(element["text-transform"] == "uppercase") { + } else if(element["@text-transform"] == "uppercase") { // all uppercase string = string.toUpperCase(); - } else if(element["text-transform"] == "capitalize") { + } else if(element["@text-transform"] == "capitalize") { // capitalize first string = string[0].toUpperCase()+string.substr(1); } @@ -1632,7 +1508,6 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli // go through and fix up unicode entities for(var i=0; i 127) { // encode unicode newString += "{\\uc0\\u"+charCode.toString()+"}"; } else if(charCode == 92) { // double backslashes @@ -1655,8 +1530,9 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli var cssAttributes = ["font-family", "font-style", "font-variant", "font-weight"]; for(var j in cssAttributes) { - if(element[cssAttributes[j]] && element[cssAttributes[j]].indexOf('"') == -1) { - style += cssAttributes[j]+":"+element[cssAttributes[j]]; + var value = element["@"+cssAttributes[j]].toString(); + if(value && value.indexOf('"') == -1) { + style += cssAttributes[j]+":"+value; } } @@ -1664,19 +1540,19 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli string = ''+string+''; } } else if(this.format == "RTF" || this.format == "Integration") { - if(element["font-style"] && (element["font-style"] == "oblique" || element["font-style"] == "italic")) { + if(element["@font-style"] == "oblique" || element["@font-style"] == "italic") { string = "\\i "+string+"\\i0 "; } - if(element["font-variant"] && element["font-variant"] == "small-caps") { + if(element["@font-variant"] == "small-caps") { string = "\\scaps "+string+"\\scaps0 "; } - if(element["font-weight"] && element["font-weight"] == "bold") { + if(element["@font-weight"] == "bold") { string = "\\b "+string+"\\b0 "; } } // add quotes if necessary - if(element.quotes) { + if(element.@quotes.length()) { this.string += this._openQuote; if(this.useBritishStyleQuotes) { @@ -1689,11 +1565,11 @@ Zotero.CSL.FormattedString.prototype.append = function(string, element, dontDeli this.string += string; - // special rule: if a field ends in a punctuation mark, and the suffix + // special rule: if a variable ends in a punctuation mark, and the suffix // begins with a period, chop the period off the suffix var suffix; - if(element && element.suffix && this.format != "compare") { - suffix = element.suffix; // copy so as to leave original intact + if(element && element.@suffix.length()) { + suffix = element.@suffix.toString(); // copy so as to leave original intact if(suffix[0] == "." && Zotero.CSL.FormattedString._punctuation.indexOf(string[string.length-1]) != -1) { @@ -1718,240 +1594,46 @@ Zotero.CSL.FormattedString.prototype.get = function() { /* * creates a new formatted string with the same formatting parameters as this one */ -Zotero.CSL.FormattedString.prototype.clone = function() { - return new Zotero.CSL.FormattedString(this.CSL, this.format); +Zotero.CSL.FormattedString.prototype.clone = function(delimiter) { + return new Zotero.CSL.FormattedString(this.CSL, this.format, delimiter); } /* - * formats a locator (pages, volume, issue) or an identifier (isbn, doi) - * note that label should be null for an identifier + * Implementation of FormattedString for sort purposes. */ -Zotero.CSL.FormattedString.prototype.appendLocator = function(identifier, number, element) { - if(number) { - var data = this.clone(); - - for(var i in element.children) { - var child = element.children[i]; - var string = ""; - - if(child.name == "number") { - string = number; - } else if(child.name == "text") { - var plural = (identifier && (number.indexOf(",") != -1 - || number.indexOf("-") != -1)); - string = this.CSL._getTerm(child["term-name"], plural, child["form"]); - } else if(identifier && child.name == "label") { - var plural = (number.indexOf(",") != -1 || number.indexOf("-") != -1); - string = this.CSL._getTerm(identifier, plural, child["form"]); - } - - if(string) { - data.append(string, child); - } - } - - this.concat(data, element); - return true; +Zotero.CSL.SortString = function() { + this.format = "Sort"; + this.string = ""; + this.delimiter = "\u0000"; // null character +} + +Zotero.CSL.SortString.prototype.concat = function(string) { + newString = string.get(); + + // Replace old delimiter if concatenated string has a delimiter as wel + if(newString.match("\u0000")) { + delimiterRegexp = new RegExp(this.delimiter, "g"); + this.delimiter += "\u0000"; + this.string = this.string.replace(delimiterRegexp, this.delimiter); + } + + // append + this.append(newString); +} + +Zotero.CSL.SortString.prototype.append = function(string) { + if(this.string) { + this.string += this.delimiter + string; } else { - return false; + this.string += string; } } -/* - * format the date in format supplied by element from the date object - * returned by this._processDate - */ -Zotero.CSL.FormattedString.prototype.appendDate = function(date, element) { - var data = this.clone(); - if(this.format == "disambiguate") { - // for disambiguation, return only the year - this.append(null, date.year); - return (date.year ? true : false); - } - - var data = this.clone(); - var isData = false; - for(var i in element.children) { - var child = element.children[i]; - var string = ""; - - if(child.name == "year" && date.year) { - if(this.format == "compare") { - string = Zotero.CSL.Global.lpad(date.year, "0", 4); - } else { - string = date.year.toString(); - if(date.disambiguation) { - string += date.disambiguation; - } - } - } else if(child.name == "month") { - if(date.month != undefined) { - if(this.format == "compare") { - string = Zotero.CSL.Global.lpad(date.month+1, "0", 2); - } else { - if(element.form == "short") { - string = this.CSL._terms["short"]["_months"][date.month]; - } else { - string = this.CSL._terms["long"]["_months"][date.month]; - } - } - } else if(date.part && this.format != "compare") { - string = date.part; - } - } else if(child.name == "day" && date.day) { - if(this.format == "compare") { - string = Zotero.CSL.Global.lpad(date.day, "0", 2); - } else { - string = date.day.toString(); - } - } else if(child.name == "text") { - string = this.CSL._getTerm(child["term-name"], false, child["form"]); - } - - if(string) { - data.append(string, child); - isData = true; - } - } - - this.concat(data, element); - - return isData; +Zotero.CSL.SortString.prototype.get = function() { + return this.string; } -/* - * THE FOLLOWING CODE IS SCHOLAR-SPECIFIC - * gets a list of possible CSL types, in order of preference, for an item - */ - Zotero.CSL.Global.optionalTypeMappings = { - journalArticle:"article-journal", - magazineArticle:"article-magazine", - newspaperArticle:"article-newspaper", - thesis:"thesis", - letter:"personal communication", - manuscript:"manuscript", - interview:"interview", - film:"motion picture", - artwork:"graphic", - webpage:"webpage", - report:"paper-conference", // ?? - 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:"paper-conference", - videoRecording:"motion picture", - tvBroadcast:"motion picture", - radioBroadcast:"motion picture", - podcast:"speech", // ?? - computerProgram:"book" // ?? -}; -// TODO: check with Elena/APA/MLA on this -Zotero.CSL.Global.fallbackTypeMappings = { - book:"book", - bookSection:"chapter", - journalArticle:"article", - magazineArticle:"article", - newspaperArticle:"article", - thesis:"book", - letter:"article", - manuscript:"book", - interview:"book", - film:"book", - artwork:"book", - webpage:"article", - report:"book", - bill:"book", - case:"book", - hearing:"book", - patent:"book", - statute:"book", - email:"article", - map:"article", - blogPost:"article", - instantMessage:"article", - forumPost:"article", - audioRecording:"article", - presentation:"article", - videoRecording:"article", - tvBroadcast:"article", - radioBroadcast:"article", - podcast:"article", - computerProgram:"book" -}; - -Zotero.CSL.prototype._getTypeFromItem = function(item) { - var scholarType = Zotero.ItemTypes.getName(item.getType()); - - // get type - Zotero.debug("CSL: parsing item of Scholar type "+scholarType); - if(Zotero.CSL.Global.optionalTypeMappings[scholarType]) { // if there is an optional type mapping - var array = [Zotero.CSL.Global.optionalTypeMappings[scholarType]]; - - // check if there is a fallback type mapping; otherwise, use article - if(Zotero.CSL.Global.fallbackTypeMappings[scholarType]) { - array.push(Zotero.CSL.Global.fallbackTypeMappings[scholarType]); - } else { - array.push("article"); - } - - return array; - } else if(Zotero.CSL.Global.fallbackTypeMappings[scholarType]) { // if there is a fallback type mapping - return [Zotero.CSL.Global.fallbackTypeMappings[scholarType]]; - } else { // use article as backup type mapping - return ["article"]; - } -} - -/* - * separate creators object into authors, editors, and translators - */ -Zotero.CSL.prototype._separateItemCreators = function(item) { - var authors = new Array(); - var editors = new Array(); - var translators = new Array(); - - var authorID = Zotero.CreatorTypes.getPrimaryIDForType(item.getType()); - var editorID = Zotero.CreatorTypes.getID("editor"); - var translatorID = Zotero.CreatorTypes.getID("translator"); - - var creators = item.getCreators(); - for each(var creator in creators) { - if(creator.creatorTypeID == editorID) { - editors.push(creator); - } else if(creator.creatorTypeID == translatorID) { - translators.push(creator); - } else if(creator.creatorTypeID == authorID) { - // TODO: do we just ignore contributors? - authors.push(creator); - } - } - - return [authors, editors, translators]; -} - -/* - * return an object containing year, month, and day - */ -Zotero.CSL.prototype._processDate = function(string) { - return Zotero.Date.strToDate(string); -} - -/* - * get a field on an item - */ -Zotero.CSL.prototype._getField = function(item, field) { - return item.getField(field, false, true); -} - -/* - * END SCHOLAR-SPECIFIC CODE - */ \ No newline at end of file +Zotero.CSL.SortString.prototype.clone = function() { + return new Zotero.CSL.SortString(); +} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/cite_compat.js b/chrome/content/zotero/xpcom/cite_compat.js new file mode 100644 index 000000000..05620372e --- /dev/null +++ b/chrome/content/zotero/xpcom/cite_compat.js @@ -0,0 +1,1881 @@ +/* + ***** 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 ***** +*/ + +/* + * Zotero.Cite: a class for creating bibliographies from within Scholar + * this class handles pulling the CSL file and item data out of the database, + * while CSL, below, handles the actual generation of the bibliography + */ +default xml namespace = "http://purl.org/net/xbiblio/csl"; + +/* + * 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.Compat = function(csl) { + this._csl = new XML(Zotero.CSL.Compat.Global.cleanXML(csl)); + + // initialize CSL + Zotero.CSL.Compat.Global.init(); + + // load localizations + this._terms = Zotero.CSL.Compat.Global.parseLocales(this._csl.terms); + + // load class defaults + this.class = this._csl["@class"].toString(); + this.hasBibliography = true; + Zotero.debug("CSL: style class is "+this.class); + + this._defaults = new Object(); + // load class defaults + if(Zotero.CSL.Compat.Global.classDefaults[this.class]) { + var classDefaults = Zotero.CSL.Compat.Global.classDefaults[this.class]; + for(var i in classDefaults) { + this._defaults[i] = classDefaults[i]; + } + } + // load defaults from CSL + this._parseFieldDefaults(this._csl.defaults); + // parse bibliography and citation options + this._parseBibliographyOptions(); + this._parseCitationOptions(); + // if no bibliography exists, parse citation element as bibliography + if(!this._bib) { + Zotero.debug("CSL: using citation element for bibliography"); + this._bib = this._cit; + this.hasBibliography = false; + } +} + + +Zotero.CSL.Compat.Global = new function() { + // for elements that inherit defaults from each other + this.inherit = { + author:"contributor", + editor:"contributor", + translator:"contributor", + pages:"locator", + volume:"locator", + issue:"locator", + isbn:"identifier", + doi:"identifier", + edition:"version" + } + + // for types + this.typeInheritance = { + "article-magazine":"article", + "article-newspaper":"article", + "article-journal":"article", + "bill":"article", + "figure":"article", + "graphic":"article", + "interview":"article", + "legal case":"article", + "manuscript":"book", + "map":"article", + "motion picture":"book", + "musical score":"article", + "pamphlet":"book", + "paper-conference":"chapter", + "patent":"article", + "personal communication":"article", + "report":"book", + "song":"article", + "speech":"article", + "thesis":"book", + "treaty":"article", + "webpage":"article", + } + + // for class definitions + this.classDefaults = new Object(); + this.classDefaults["author-date"] = { + author:{ + substitute:[ + {name:"editor"}, + {name:"translator"}, + {name:"titles", relation:"container", "font-style":"italic"}, + {name:"titles", children:[ + {name:"title", form:"short"} + ]} + ] + } + }; + + + + this.ns = "http://purl.org/net/xbiblio/csl"; + + /* + * initializes CSL interpreter + */ + this.init = function() { + if(!Zotero.CSL.Compat.Global._xmlLang) { + // get XML lang + Zotero.CSL.Compat.Global._xmlLang = Zotero.locale; + + // read locales.xml from directory + var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(); + req.open("GET", "chrome://zotero/locale/locales.xml", false); + req.overrideMimeType("text/plain"); + req.send(null); + + // get default terms + var locales = new XML(Zotero.CSL.Compat.Global.cleanXML(req.responseText)); + Zotero.CSL.Compat.Global._defaultTerms = Zotero.CSL.Compat.Global.parseLocales(locales, true); + } + } + + /* + * returns an array of short or long month strings + */ + this.getMonthStrings = function(form) { + Zotero.CSL.Compat.Global.init(); + return Zotero.CSL.Compat.Global._defaultTerms[form]["_months"]; + } + + /* + * removes parse instructions from XML + */ + this.cleanXML = function(xml) { + return xml.replace(/<\?[^>]*\?>/g, ""); + } + + /* + * parses locale strings into Zotero.CSL.Compat.Global._defaultTerms + */ + this.parseLocales = function(termXML, ignoreLang) { + // return defaults if there are no terms + if(!termXML.length()) { + return (Zotero.CSL.Compat.Global._defaultTerms ? Zotero.CSL.Compat.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.Compat.Global._xmlLang); + if(!locale.length()) { + var xmlLang = Zotero.CSL.Compat.Global._xmlLang.substr(0, 2); + locale = termXML.locale.(@xml::lang == xmlLang); + } + if(!locale.length()) { + // return defaults if there are no locales + return (Zotero.CSL.Compat.Global._defaultTerms ? Zotero.CSL.Compat.Global._defaultTerms : {}); + } + } + + var termArray = new Object(); + termArray["default"] = new Object(); + + if(Zotero.CSL.Compat.Global._defaultTerms) { + // ugh. copy default array. javascript dumb. + for(var i in Zotero.CSL.Compat.Global._defaultTerms) { + termArray[i] = new Object(); + for(var j in Zotero.CSL.Compat.Global._defaultTerms[i]) { + if(typeof(Zotero.CSL.Compat.Global._defaultTerms[i]) == "object") { + termArray[i][j] = [Zotero.CSL.Compat.Global._defaultTerms[i][j][0], + Zotero.CSL.Compat.Global._defaultTerms[i][j][1]]; + } else { + termArray[i][j] = Zotero.CSL.Compat.Global_defaultTerms[i][j]; + } + } + } + } + + // loop through terms + for each(var term in locale.term) { + var name = term.@name.toString(); + if(!name) { + throw("citations cannot be generated: no name defined on term in CSL"); + } + // 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(); + } + } + } + + return termArray; + } + + /* + * pads a number or other string with a given string on the left + */ + this.lpad = function(string, pad, length) { + while(string.length < length) { + string = pad + string; + } + return string; + } +} + +/* + * preprocess items, separating authors, editors, and translators arrays into + * separate properties + * + * must be called prior to generating citations or bibliography with a new set + * of items + */ +Zotero.CSL.Compat.prototype.generateItemSet = function(items) { + Zotero.debug("CSL: preprocessing items"); + + this._ignore = null; + + // get data necessary to generate citations before sorting + for(var i in items) { + var item = items[i]; + var dateModified = this._getField(item, "dateModified"); + + if(!item._csl || item._csl.dateModified != dateModified) { + // namespace everything in item._csl so there's no chance of overlap + item._csl = new Object(); + item._csl.dateModified = dateModified; + + // separate item into authors, editors, translators + var creators = this._separateItemCreators(item); + item._csl.authors = creators[0]; + item._csl.editors = creators[1]; + item._csl.translators = creators[2]; + + // parse date + item._csl.date = Zotero.CSL.Compat.prototype._processDate(this._getField(item, "date")); + } + // clear disambiguation and subsequent author substitute + if(item._csl.date && item._csl.date.disambiguation) item._csl.date.disambiguation = undefined; + if(item._csl.subsequentAuthorSubstitute) item._csl.subsequentAuthorSubstitute = undefined; + } + + // sort by sort order + if(this._bib.sortOrder) { + Zotero.debug("CSL: sorting items"); + var me = this; + items.sort(function(a, b) { + return me._compareItem(a, b); + }); + } + + // disambiguate items after preprocessing and sorting + var usedCitations = new Array(); + var lastAuthors; + + for(var i in items) { + var item = items[i]; + + // handle subsequent author substitutes + if(item._csl.authors.length && lastAuthors) { + var authorsAreSame = true; + for(var i=item._csl.authors.length-1; i>=0; i--) { + if(!lastAuthors[i] || + lastAuthors[i].firstName != item._csl.authors[i].firstName || + lastAuthors[i].lastName != item._csl.authors[i].lastName || + lastAuthors[i].creatorType != item._csl.authors[i].creatorType) { + authorsAreSame = false; + break; + } + } + if(authorsAreSame) item._csl.subsequentAuthorSubstitute = true; + } + lastAuthors = item._csl.authors; + + // handle (2006a) disambiguation for author-date styles + if(this.class == "author-date") { + var year = item._csl.date.year; + + if(authorsAreSame) { + if(usedCitations[year]) { + if(!usedCitations[year]._csl.date.disambiguation) { + usedCitations[year]._csl.date.disambiguation = "a"; + item._csl.date.disambiguation = "b"; + } else { + // get all but last character + var oldLetter = usedCitations[year]._csl.date.disambiguation; + if(oldLetter.length > 1) { + item._csl.date.disambiguation = oldLetter.substr(0, oldLetter.length-1); + } else { + item._csl.date.disambiguation = ""; + } + + var charCode = oldLetter.charCodeAt(oldLetter.length-1); + if(charCode == 122) { + // item is z; add another letter + item._csl.date.disambiguation += "za"; + } else { + // next lowercase letter + item._csl.date.disambiguation += String.fromCharCode(charCode+1); + } + } + } + } else { + usedCitations = new Array(); + } + + usedCitations[year] = item; + } + + // add numbers to each + item._csl.number = i; + } + + // for compatibility, create an itemSet object + var me = this; + itemSet = new Object(); + itemSet.items = items; + itemSet.resort = function() { this.items = me.generateItemSet(items).items }; + + return itemSet; +} + +Zotero.CSL.Compat._locatorTerms = { + p:"page", + g:"paragraph", + l:"line" +}; + +Zotero.CSL.Compat.prototype.createCitation = function(itemSet, ids, format, position, locators, locatorTypes) { + var items = Zotero.Items.get(ids); + + var string = new Zotero.CSL.Compat.FormattedString(this, format); + if(position == "ibid" || position == "ibid-pages") { // indicates ibid + var term = this._getTerm("ibid"); + string.append(term[0].toUpperCase()+term.substr(1)); + + if(position == "ibid-pages") { + // locator data + var locator = locators[0]; + + if(locator) { + var locatorType = Zotero.CSL.Compat._locatorTerms[locatorTypes[0]]; + + // search for elements with the same serialization + var element = this._getFieldDefaults("locator"); + if(!element) { + element = { + name:"locator", + children:{name:"number"} + }; + } + + if(element) { + string.append("., "); + string.appendLocator(locatorType, locator, element); + } + } + } + } else { // indicates primary or subsequent + var lasti = items.length-1; + for(var i in items) { + var locatorType = false; + var locator = false; + if(locators) { + locatorType = Zotero.CSL.Compat._locatorTerms[locatorTypes[i]]; + locator = locators[i]; + } + + var item = items[i]; + + var citationString = this._getCitation(item, + position, + locatorType, locator, format, this._cit); + string.concat(citationString); + + if(this._cit.format && this._cit.format.delimiter && i != lasti) { + // add delimiter if one exists, and this isn't the last element + string.append(this._cit.format.delimiter); + } + } + } + + // add format + if(this._cit.format) { + // add citation prefix or suffix + if(this._cit.format.prefix) { + string.string = this._cit.format.prefix + string.string; + } + if(this._cit.format.suffix) { + string.append(this._cit.format.suffix); + } + } + + return string.get(); +} + +/* + * create a bibliography + * (items is expected to be an array of items) + */ +Zotero.CSL.Compat.prototype.createBibliography = function(itemSet, format) { + var items = itemSet.items; + var output = ""; + + var index = 0; + if(format == "HTML") { + if(this.class == "note" && this._bib == this._cit) { + output += '
      \r\n'; + } else if(this._bib.hangingIndent) { + output += '
      \r\n'; + } + } else if(format == "RTF") { + output += "{\\rtf\\ansi{\\fonttbl\\f0\\froman Times New Roman;}{\\colortbl;\\red255\\green255\\blue255;}\\pard\\f0"; + if(this._bib.hangingIndent) { + output += "\\li720\\fi-720"; + } + output += "\r\n"; + } + + for(var i in items) { + var item = items[i]; + + var string = this._getCitation(item, "first", false, false, format, this._bib).get(); + if(!string) { + continue; + } + + // add format + if(this._bib.format) { + // add citation prefix or suffix + if(this._bib.format.prefix) { + string = this._bib.format.prefix + string ; + } + if(this._bib.format.suffix) { + string += this._bib.format.suffix; + } + } + + // add line feeds + if(format == "HTML") { + var coins = Zotero.OpenURL.createContextObject(item, "1.0"); + if(coins) { + string += ''; + } + + if(this.class == "note" && this._bib == this._cit) { + output += "
    1. "+string+"
    2. \r\n"; + } else { + output += "

      "+string+"

      \r\n"; + } + } else if(format == "RTF") { + if(this.class == "note" && this._bib == this._cit) { + index++; + output += index+". "; + } + output += string+"\\\r\n\\\r\n"; + } else { + if(format == "Text" && this.class == "note" && this._bib == this._cit) { + index++; + output += index+". "; + } + // attach \n on mac (since both \r and \n count as newlines for + // clipboard purposes) + output += string+(Zotero.isMac ? "\n\n" : "\r\n\r\n"); + } + } + + if(format == "HTML") { + if(this.class == "note" && this._bib == this._cit) { + output += '
    '; + } else if(this._bib.hangingIndent) { + output += ''; + } + } else if(format == "RTF") { + // drop last 6 characters of output (last two returns) + output = output.substr(0, output.length-6)+"}"; + } else { + // drop last 4 characters (last two returns) + output = output.substr(0, (Zotero.isMac ? output.length-2 : output.length-4)); + } + + return output; +} + +/* + * parses attributes and children for a CSL field + */ +Zotero.CSL.Compat.prototype._parseFieldAttrChildren = function(element, desc, ignoreChildren) { + if(!desc) { + var desc = new Object(); + } + + // copy attributes + var attributes = element.attributes(); + for each(var attribute in attributes) { + desc[attribute.name()] = attribute.toString(); + } + + var children = element.children(); + if(!ignoreChildren) { + if(children.length()) { + // parse children + + if(children.length() > element.substitute.length()) { + // if there are non-substitute children, clear the current children + // array + desc.children = new Array(); + } + + // add children to children array + for each(var child in children) { + if(child.namespace() == Zotero.CSL.Compat.Global.ns) { // ignore elements in other + // namespaces + // parse recursively + var name = child.localName(); + if(name == "substitute") { + // place substitutes in their own key, so that they're + // overridden separately + if(child.choose.length) { // choose + desc.substitute = new Array(); + + var chooseChildren = child.choose.children(); + for each(var choose in chooseChildren) { + if(choose.namespace() == Zotero.CSL.Compat.Global.ns) { + var option = new Object(); + option.name = choose.localName(); + this._parseFieldAttrChildren(choose, option); + desc.substitute.push(option); + } + } + } else { // don't choose + desc.substitute = child.text().toString(); + } + } else { + var childDesc = this._parseFieldAttrChildren(child); + childDesc.name = name; + desc.children.push(childDesc); + } + } + } + } + } + + return desc; +} + +/* + * parses a list of fields into a defaults associative array + */ +Zotero.CSL.Compat.prototype._parseFieldDefaults = function(ref) { + for each(var element in ref.children()) { + if(element.namespace() == Zotero.CSL.Compat.Global.ns) { // ignore elements in other namespaces + var name = element.localName(); + Zotero.debug("CSL: parsing field defaults for "+name); + var fieldDesc = this._parseFieldAttrChildren(element); + + if(this._defaults[name]) { // inherit from existing defaults + this._defaults[name] = this._merge(this._defaults[name], + fieldDesc); + } else { + this._defaults[name] = fieldDesc; + } + } + } +} + +/* + * parses a list of fields into an array of objects + */ +Zotero.CSL.Compat.prototype._parseFields = function(ref, position, type, bibCitElement, inheritFormat) { + var typeDesc = new Array(); + for each(var element in ref) { + if(element.namespace() == Zotero.CSL.Compat.Global.ns) { // ignore elements in other namespaces + var itemDesc = new Object(); + itemDesc.name = element.localName(); + + // add defaults, but only if we're parsing as a reference type + if(type != undefined) { + var fieldDefaults = this._getFieldDefaults(itemDesc.name); + itemDesc = this._merge(fieldDefaults, itemDesc); + if(bibCitElement && inheritFormat) { + itemDesc = this._merge(bibCitElement.inheritFormat, itemDesc); + } + } + + // parse group children + if(itemDesc.name == "group" || itemDesc.name == "conditional" || + itemDesc.name == "if" || itemDesc.name == "else-if" || + itemDesc.name == "else") { + // parse attributes on this field, but ignore children + this._parseFieldAttrChildren(element, itemDesc, true); + + var children = element.children(); + if(children.length()) { + itemDesc.children = this._parseFields(children, position, type, bibCitElement); + } + } else { + // parse attributes on this field + this._parseFieldAttrChildren(element, itemDesc); + } + + if(type != undefined) { + // create serialized representation + itemDesc._serialized = this._serializeElement(itemDesc.name, itemDesc); + // add to serialization for type + if(bibCitElement && bibCitElement._serializations) { + bibCitElement._serializations[position][type][itemDesc._serialized] = itemDesc; + } + } + + typeDesc.push(itemDesc); + } + } + return typeDesc; +} + +/* + * parses an et al field + */ +Zotero.CSL.Compat.prototype._parseEtAl = function(etAl, bibCitElement) { + if(etAl.length()) { + bibCitElement.etAl = new Object(); + + if(etAl.length() > 1) { + // separate first and subsequent et als + for each(var etAlElement in etAl) { + if(etAlElement.@position == "subsequent") { + bibCitElement.subsequentEtAl = new Object(); + bibCitElement.subsequentEtAl.minCreators = parseInt(etAlElement['@min-authors'], 10); + bibCitElement.subsequentEtAl.useFirst = parseInt(etAlElement['@use-first'], 10); + } else { + var parseElement = etAlElement; + } + } + } else { + var parseElement = etAl; + } + + bibCitElement.etAl.minCreators = parseInt(parseElement['@min-authors'], 10); + bibCitElement.etAl.useFirst = parseInt(parseElement['@use-first'], 10); + } else if(this._defaults["et-al"]) { + bibCitElement.etAl = new Object(); + + bibCitElement.etAl.minCreators = parseInt(this._defaults["et-al"]['min-authors'], 10); + bibCitElement.etAl.useFirst = parseInt(this._defaults["et-al"]['use-first'], 10); + } +} + +/* + * parses cs-format attributes into just a prefix and a suffix; accepts an + * optional array of cs-format + */ +Zotero.CSL.Compat.prototype._parseBibliographyOptions = function() { + if(!this._csl.bibliography.length()) { + return; + } + + var bibliography = this._csl.bibliography; + this._bib = new Object(); + + // global prefix and suffix format information + this._bib.inheritFormat = new Array(); + for each(var attribute in bibliography.layout.item.attributes()) { + this._bib.inheritFormat[attribute.name()] = attribute.toString(); + } + + // sections (TODO) + this._bib.sections = [{groupBy:"default", + heading:bibliography.layout.heading.text["@term-name"].toString()}]; + for each(var section in bibliography.layout.section) { + this._bib.sections.push([{groupBy:section["@group-by"].toString(), + heading:section.heading.text["@term-name"].toString()}]); + } + + // subsequent author substitute + // replaces subsequent occurances of an author with a given string + if(bibliography['@subsequent-author-substitute']) { + this._bib.subsequentAuthorSubstitute = bibliography['@subsequent-author-substitute'].toString(); + } + + // hanging indent + if(bibliography['@hanging-indent']) { + this._bib.hangingIndent = true; + } + + // sort order + var algorithm = bibliography.sort.@algorithm.toString(); + if(algorithm) { + // for classes, use the sort order that + if(algorithm == "author-date") { + this._bib.sortOrder = [this._getFieldDefaults("author"), + this._getFieldDefaults("date"), + this._getFieldDefaults("titles")]; + this._bib.sortOrder[0].name = "author"; + this._bib.sortOrder[0]["name-as-sort-order"] = "all"; + this._bib.sortOrder[1].name = "date"; + this._bib.sortOrder[2].name = "titles"; + } else if(algorithm == "label") { + this._bib.sortOrder = [this._getFieldDefaults("label")]; + this._bib.sortOrder[0].name = "label"; + } else if(algorithm == "cited") { + this._bib.sortOrder = [this._getFieldDefaults("cited")]; + this._bib.sortOrder[0].name = "cited"; + } + } else { + this._bib.sortOrder = this._parseFields(bibliography.sort.children(), "first", false, this._bib); + } + + // parse et al + this._parseEtAl(bibliography["et-al"], this._bib); + + // parse types + this._parseTypes(this._csl.bibliography.layout.item, this._bib); +} + +/* + * parses cs-format attributes into just a prefix and a suffix; accepts an + * optional array of cs-format + */ +Zotero.CSL.Compat.prototype._parseCitationOptions = function() { + var citation = this._csl.citation; + this._cit = new Object(); + + // parse et al + this._parseEtAl(citation["et-al"], this._cit); + + // global format information + this._cit.format = new Array(); + for each(var attribute in citation.attributes()) { + this._cit.format[attribute.name()] = attribute.toString(); + } + + // parse types + this._parseTypes(this._csl.citation.layout.item, this._cit); +} + +/* + * determine available reference types and add their XML objects to the tree + * (they will be parsed on the fly when necessary; see _parseReferenceType) + */ +Zotero.CSL.Compat.prototype._parseTypes = function(itemElements, bibCitElement) { + Zotero.debug("CSL: parsing item elements"); + + bibCitElement._types = new Object(); + bibCitElement._serializations = new Object(); + + // find the type item without position="subsequent" + for each(var itemElement in itemElements) { + var position = itemElement.@position.toString(); + if(position) { + // handle ibids + if(position == "subsequent" && + itemElement.@ibid.toString() == "true") { + this.ibid = true; + } + } else { + position = "first"; + } + + if(!bibCitElement._types[position]) { + bibCitElement._types[position] = new Object(); + bibCitElement._serializations[position] = new Object(); + } + + // create an associative array of available types + if(itemElement.choose.length()) { + for each(var type in itemElement.choose.type) { + bibCitElement._types[position][type.@name] = type; + bibCitElement._serializations[position][type.@name] = new Object(); + } + } else { + // if there's only one type, bind it to index 0 + bibCitElement._types[position][0] = itemElement; + bibCitElement._serializations[position][0] = new Object(); + } + } +} + +/* + * convert reference types to native structures for speed + */ +Zotero.CSL.Compat.prototype._getTypeObject = function(position, reftype, bibCitElement) { + if(!bibCitElement._types[position][reftype]) { + // no type available + return false; + } + + // parse type if necessary + if(typeof(bibCitElement._types[position][reftype]) == "xml") { + Zotero.debug("CSL: parsing XML for "+reftype); + bibCitElement._types[position][reftype] = this._parseFields( + bibCitElement._types[position][reftype].children(), + position, reftype, bibCitElement, true); + } + + Zotero.debug("CSL: got object for "+reftype); + return bibCitElement._types[position][reftype]; +} + +/* + * merges two elements, letting the second override the first + */ +Zotero.CSL.Compat.prototype._merge = function(element1, element2) { + var mergedElement = new Object(); + for(var i in element1) { + mergedElement[i] = element1[i]; + } + for(var i in element2) { + mergedElement[i] = element2[i]; + } + return mergedElement; +} + +/* + * gets defaults for a specific element; handles various inheritance rules + * (contributor, locator) + */ +Zotero.CSL.Compat.prototype._getFieldDefaults = function(elementName) { + // first, see if there are specific defaults + if(this._defaults[elementName]) { + if(Zotero.CSL.Compat.Global.inherit[elementName]) { + var inheritedDefaults = this._getFieldDefaults(Zotero.CSL.Compat.Global.inherit[elementName]); + for(var i in inheritedDefaults) { // will only be called if there + // is merging necessary + return this._merge(inheritedDefaults, this._defaults[elementName]); + } + } + return this._defaults[elementName]; + } + // next, try to get defaults from the item from which this item inherits + if(Zotero.CSL.Compat.Global.inherit[elementName]) { + return this._getFieldDefaults(Zotero.CSL.Compat.Global.inherit[elementName]); + } + // finally, return an empty object + return {}; +} + +/* + * gets a term, in singular or plural form + */ +Zotero.CSL.Compat.prototype._getTerm = function(term, plural, form) { + if(!form) { + form = "long"; + } + if(!this._terms[form][term]) { + return ""; + } + + if(typeof(this._terms[form][term]) == "object") { // singular and plural forms + // are available + if(plural) { + return this._terms[form][term][1]; + } else { + return this._terms[form][term][0]; + } + } + + return this._terms[form][term]; +} + + +/* + * serializes an element into a string suitable to prevent substitutes from + * recurring in the same style + */ +Zotero.CSL.Compat.prototype._serializeElement = function(name, element) { + var string = name; + if(element.relation) { + string += " relation:"+element.relation; + } + if(element.role) { + string += " role:"+element.role; + } + return string; +} + +/* + * handles sorting of items + */ +Zotero.CSL.Compat.prototype._compareItem = function(a, b, opt) { + var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"] + .getService(Components.interfaces.nsILocaleService); + var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"] + .getService(Components.interfaces.nsICollationFactory); + var collation = collationFactory.CreateCollation(localeService.getApplicationLocale()); + + for(var i in this._bib.sortOrder) { + var sortElement = this._bib.sortOrder[i]; + + if(sortElement.name == "date") { + var aValue = a.getField("date", true); + var bValue = b.getField("date", true); + + if(bValue == "" && aValue != "") { + return -1; + } else if(aValue == "" && bValue != "") { + return 1; + } else if(bValue > aValue) { + return -1; + } else if(bValue < aValue) { + return 1; + } + } else { + var formattedStringA = new Zotero.CSL.Compat.FormattedString(this, "compare"); + var formattedStringB = new Zotero.CSL.Compat.FormattedString(this, "compare"); + + //Zotero.debug('comparing '+sortElement.name+' on "'+a.getField("title")+'" and "'+b.getField("title")+'"'); + + this._getFieldValue(sortElement.name, sortElement, a, + formattedStringA, this._bib); + this._getFieldValue(sortElement.name, sortElement, b, + formattedStringB, this._bib); + + var aValue = formattedStringA.get().toLowerCase(); + var bValue = formattedStringB.get().toLowerCase(); + //Zotero.debug(aValue+" vs "+bValue); + + var cmp = collation.compareString(0, aValue, bValue); + if(cmp != 0) { + return cmp; + } + } + } + + // finally, give up; they're the same + return 0; +} + +/* + * 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.Compat.prototype._processCreators = function(type, element, creators, format, bibCitElement, position) { + var maxCreators = creators.length; + if(!maxCreators) return false; + + var data = new Zotero.CSL.Compat.FormattedString(this, format); + if(format == "disambiguate") { + // for disambiguation, return only the last name of the first creator + // TODO: is this right? + data.append(creators[0].lastName); + return data; + } + + if(!element.children) { + return false; + } + + var etAl = bibCitElement.etAl; + if(position == "subsequent" && bibCitElement.subsequentEtAl) { + etAl = bibCitElement.subsequentEtAl; + } + + for(var i in element.children) { + var child = element.children[i]; + var string = ""; + + if(child.name == "name") { + var useEtAl = false; + + // figure out if we need to use "et al" + if(etAl && maxCreators >= etAl.minCreators) { + maxCreators = etAl.useFirst; + useEtAl = true; + } + + // parse authors into strings + var authorStrings = []; + var firstName, lastName; + for(var i=0; i 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"]) { + 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 + Zotero.debug(child["delimiter-precedes-last"]); + if((authorStrings.length == 2 && child["delimiter-precedes-last"] != "always") || + (authorStrings.length > 2 && child["delimiter-precedes-last"] == "never")) { + var lastString = authorStrings.pop(); + authorStrings[authorStrings.length-1] = authorStrings[authorStrings.length-1]+" "+lastString; + } + } + string = authorStrings.join(joinString); + } else if(child.name == "label") { + string = this._getTerm(type, (maxCreators != 1), child["form"]); + } + + + // add string to data + if(string) { + data.append(string, child); + } + } + + // add to the data + return data; +} + +/* + * get a citation, given an item and bibCitElement + */ +Zotero.CSL.Compat.prototype._getCitation = function(item, position, locatorType, locator, format, bibCitElement) { + Zotero.debug("CSL: generating citation for item "+item.getID()); + + // use true position if possible, otherwise "first" + var typePosition = (bibCitElement._types[position] ? position : "first"); + + // determine mapping + if(bibCitElement._types[typePosition][0]) { + // only one element + var typeName = 0; + var type = this._getTypeObject(typePosition, typeName, bibCitElement); + } else { + var typeNames = this._getTypeFromItem(item); + for each(var typeName in typeNames) { + var type = this._getTypeObject(typePosition, typeName, bibCitElement); + if(type) { + break; + } + } + } + + Zotero.debug("CSL: using CSL type "+typeName); + + // remove previous ignore entries from list + this._ignore = new Array(); + + var formattedString = new Zotero.CSL.Compat.FormattedString(this, format); + for(var j in type) { + this._getFieldValue(type[j].name, type[j], item, formattedString, + bibCitElement, position, locatorType, locator, + typeName); + } + + return formattedString; +} + +/* + * processes an element from a (pre-processed) item into text + */ +Zotero.CSL.Compat.prototype._getFieldValue = function(name, element, item, formattedString, + bibCitElement, position, + locatorType, locator, typeName) { + + + var dataAppended = false; + var itemID = item.getID(); + + if(element._serialized && this._ignore && this._ignore[element._serialized]) { + return false; + } + + if(name == "author") { + if(item._csl.subsequentAuthorSubstitute && bibCitElement.subsequentAuthorSubstitute) { + // handle subsequent author substitute behavior + dataAppended = formattedString.append(bibCitElement.subsequentAuthorSubstitute, element); + } else { + var newString = this._processCreators(name, element, item._csl.authors, formattedString.format, bibCitElement, position); + if(newString) dataAppended = formattedString.concat(newString, element); + } + } else if(name == "editor") { + dataAppended = formattedString.concat(this._processCreators(name, element, item._csl.editors, formattedString.format, bibCitElement, position), element); + } else if(name == "translator") { + dataAppended = formattedString.concat(this._processCreators(name, element, item._csl.translators, formattedString.format, bibCitElement, position), element); + } else if(name == "titles") { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format); + + for(var i in element.children) { + var child = element.children[i]; + var string = null; + + if(child.name == "title") { // for now, we only care about the + // "title" sub-element + if(!element.relation) { + // preferentially use shortTitle if flagged + if(element.form && element.form == "short") { + string = this._getField(item, "shortTitle"); + } + if(!string) { + string = this._getField(item, "title"); + } + } else if(element.relation == "container") { + string = this._getField(item, "publicationTitle"); + } else if(element.relation == "collection") { + string = this._getField(item, "seriesTitle"); + if(!string) string = this._getField(item, "series"); + } else if(element.relation == "event") { + string = this._getField(item, "conferenceName"); + } + + // if comparing, drop "a" or "the" from title + if(formattedString.format == "compare" && string.length > 1) { + if(string.substr(0, 2).toLowerCase() == "a ") { + string = string.substr(2); + } else if(string.length > 3 && string.substr(0, 4).toLowerCase() == "the ") { + string = string.substr(4); + } + } + } + + if(string) { + data.append(string, child); + } + } + + dataAppended = formattedString.concat(data, element); + } else if(name == "date") { + dataAppended = formattedString.appendDate(item._csl.date, element); + } else if(name == "publisher") { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format); + + for(var i in element.children) { + var child = element.children[i]; + var string = ""; + + if(child.name == "place") { + string = this._getField(item, "place"); + } else if(child.name == "name") { + string = this._getField(item, "publisher"); + } + + if(string) { + data.append(string, child); + } + } + + dataAppended = formattedString.concat(data, element); + } else if(name == "access") { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format); + var text = null; + var save = false; + + for(var i in element.children) { + text = null; + var child = element.children[i]; + + if(child.name == "url") { + text = this._getField(item, "url"); + } else if(child.name == "date") { + var field = this._getField(item, "accessDate"); + if(field) { + data.appendDate(this._processDate(field), child); + save = true; + } + } else if(child.name == "physicalLocation") { + text = this._getField(item, "archiveLocation"); + } else if(child.name == "text") { + text = this._getTerm(child["term-name"], false, child["form"]); + } + + if(text) { + data.append(text, child); + if(child.name != "text") { + // only save if there's non-text data + save = true; + } + } + } + + if(save) { + dataAppended = formattedString.concat(data, element); + } + } else if(name == "volume" || name == "issue") { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format); + + var field = this._getField(item, name); + if(field) { + dataAppended = formattedString.appendLocator(name, field, element); + } + } else if(name == "pages") { + if(locatorType == "page") { + var field = locator; + } else if(typeName != "book") { + var field = this._getField(item, "pages"); + } + + if(field) { + dataAppended = formattedString.appendLocator("page", field, element); + } + } else if(name == "locator") { + if(locator) { + dataAppended = formattedString.appendLocator(locatorType, locator, element); + } + } else if(name == "edition") { + dataAppended = formattedString.append(this._getField(item, "edition"), element); + } else if(name == "genre") { + var data = this._getField(item, "type"); + if(!data) { + data = this._getField(item, "thesisType"); + } + dataAppended = formattedString.append(data, element); + } else if(name == "group") { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format, element["delimiter"]); + + for(var i in element.children) { + // get data for each child element + var child = element.children[i]; + + this._getFieldValue(child.name, child, item, data, bibCitElement, + position, typeName); + } + + dataAppended = formattedString.concat(data, element); + } else if(name == "conditional") { + var status = false; + for(var i in element.children) { + var condition = element.children[i]; + + if(condition.name == "if" || condition.name == "else-if") { + // evaluate condition for if/else if + if(condition.type) { + var typeNames = this._getTypeFromItem(item); + for each(var typeName in typeNames) { + if(typeName == condition.type) { + status = true; + break; + } + } + } else if(condition.field) { + var testString = new Zotero.CSL.Compat.FormattedString(this, "Text"); + status = this._getFieldValue(condition.field, this._getFieldDefaults(condition.field), item, + testString, bibCitElement); + } + } else if(condition.name == "else") { + status = true; + } + + if(status) { + var data = new Zotero.CSL.Compat.FormattedString(this, formattedString.format, element["delimiter"]); + for(var j in condition.children) { + // get data for each child element + var child = condition.children[j]; + + this._getFieldValue(child.name, child, item, data, bibCitElement, + position, typeName); + } + dataAppended = formattedString.concat(data, condition); + break; + } + } + } else if(name == "text") { + dataAppended = formattedString.append(this._getTerm(element["term-name"], false, element["form"]), element); + } else if(name == "isbn" || name == "doi") { + var field = this._getField(item, name.toUpperCase()); + if(field) { + dataAppended = formattedString.appendLocator(null, field, element); + } + } else if(name == "number") { + dataAppended = formattedString.append(this._csl.number, element); + } + + // if no change and there's a substitute, try it + if(dataAppended) { + return true; + } else if (element.substitute) { + // try each substitute element until one returns something + for(var i in element.substitute) { + var substituteElement = element.substitute[i]; + var serialization = this._serializeElement(substituteElement.name, + substituteElement); + + var inheritElement; + if(Zotero.CSL.Compat.Global.inherit[substituteElement.name] && Zotero.CSL.Compat.Global.inherit[name] + && Zotero.CSL.Compat.Global.inherit[substituteElement.name] == Zotero.CSL.Compat.Global.inherit[name]) { + // if both substituteElement and the parent element inherit from + // the same base element, apply styles here + inheritElement = element; + } else { + // search for elements with the same serialization + if(typeName != undefined && bibCitElement._serializations[position] + && bibCitElement._serializations[position][typeName] + && bibCitElement._serializations[position][typeName][serialization]) { + inheritElement = bibCitElement._serializations[position][typeName][serialization]; + } else { + // otherwise, use defaults + inheritElement = this._getFieldDefaults(substituteElement.name); + } + } + + // merge inheritElement and element + substituteElement = this._merge(inheritElement, substituteElement); + // regardless of inheritance pathway, make sure substitute inherits + // general prefix and suffix from the element it's substituting for + substituteElement.prefix = element.prefix; + substituteElement.suffix = element.suffix; + substituteElement.form = element.form; + // clear substitute element off of the element we're substituting + substituteElement.substitute = undefined; + + // get field value + dataAppended = this._getFieldValue(substituteElement.name, + substituteElement, item, formattedString, + bibCitElement, position, typeName); + + // ignore elements with the same serialization + if(this._ignore) { // array might not exist if doing disambiguation + this._ignore[serialization] = true; + } + + // return field value, if there is one; otherwise, keep processing + // the data + if(dataAppended) { + return true; + } + } + } + + return false; +} + + +Zotero.CSL.Compat.FormattedString = function(CSL, format, delimiter) { + this.CSL = CSL; + this.format = format; + this.delimiter = delimiter; + this.string = ""; + this.closePunctuation = false; + this.useBritishStyleQuotes = false; + + if(format == "RTF") { + this._openQuote = "\\uc0\\u8220 "; + this._closeQuote = "\\uc0\\u8221 "; + } else { + this._openQuote = "\u201c"; + this._closeQuote = "\u201d"; + } +} + +Zotero.CSL.Compat.FormattedString._punctuation = ["!", ".", ",", "?"]; + +/* + * attaches another formatted string to the end of the current one + */ +Zotero.CSL.Compat.FormattedString.prototype.concat = function(formattedString, element) { + if(!formattedString || !formattedString.string) { + return false; + } + + if(formattedString.format != this.format) { + throw "CSL: cannot concatenate formatted strings: formats do not match"; + } + + if(formattedString.string) { + // first, append the actual string + var haveAppended = this.append(formattedString.string, element, false, true); + + // if there's close punctuation to append, that also counts + if(formattedString.closePunctuation) { + haveAppended = true; + if(this.closePunctuation) { + // if there's existing close punctuation and punctuation to + // append, we need to append that + this.string += this.closePunctuation; + } + // add the new close punctuation + this.closePunctuation = formattedString.closePunctuation; + } + + return haveAppended; + } + return false; +} + +/* + * appends a string (with format parameters) to the current one + */ +Zotero.CSL.Compat.FormattedString.prototype.append = function(string, element, dontDelimit, dontEscape) { + if(!string) return false; + if(typeof(string) != "string") { + string = string.toString(); + } + + // append delimiter if necessary + if(this.delimiter && this.string && !dontDelimit) { + this.append(this.delimiter, null, true); + } + + // append prefix before closing punctuation + if(element && element.prefix && this.format != "compare") { + this.append(element.prefix, null, true); + } + + // close quotes, etc. using punctuation + if(this.closePunctuation) { + if(Zotero.CSL.Compat.FormattedString._punctuation.indexOf(string[0]) != -1) { + this.string += string[0]; + string = string.substr(1); + } + this.string += this.closePunctuation; + this.closePunctuation = false; + } + + // handle text transformation + if(element) { + if(element["text-transform"]) { + if(element["text-transform"] == "lowercase") { + // all lowercase + string = string.toLowerCase(); + } else if(element["text-transform"] == "uppercase") { + // all uppercase + string = string.toUpperCase(); + } else if(element["text-transform"] == "capitalize") { + // capitalize first + string = string[0].toUpperCase()+string.substr(1); + } + } + } + + if(!dontEscape) { + if(this.format == "HTML") { + var newString = ""; + + for(var i=0; i + newString += '>'; + break; + case 8211: // en-dash + newString += '–' + break; + case 8212: // em-dash + newString += '—' + break; + default: + newString += string[i]; + } + } + + string = newString; + + } else if(this.format == "RTF") { + var newString = ""; + + // go through and fix up unicode entities + for(var i=0; i 127) { // encode unicode + newString += "{\\uc0\\u"+charCode.toString()+"}"; + } else if(charCode == 92) { // double backslashes + newString += "\\\\"; + } else { + newString += string[i]; + } + } + + string = newString; + } else if(this.format == "Integration") { + string = string.replace(/\\/g, "\\\\"); + } + } + + if(element) { + if(this.format == "HTML") { + var style = ""; + + var cssAttributes = ["font-family", "font-style", "font-variant", + "font-weight"]; + for(var j in cssAttributes) { + if(element[cssAttributes[j]] && element[cssAttributes[j]].indexOf('"') == -1) { + style += cssAttributes[j]+":"+element[cssAttributes[j]]; + } + } + + if(style) { + string = ''+string+''; + } + } else if(this.format == "RTF" || this.format == "Integration") { + if(element["font-style"] && (element["font-style"] == "oblique" || element["font-style"] == "italic")) { + string = "\\i "+string+"\\i0 "; + } + if(element["font-variant"] && element["font-variant"] == "small-caps") { + string = "\\scaps "+string+"\\scaps0 "; + } + if(element["font-weight"] && element["font-weight"] == "bold") { + string = "\\b "+string+"\\b0 "; + } + } + + // add quotes if necessary + if(element.quotes) { + this.string += this._openQuote; + + if(this.useBritishStyleQuotes) { + string += this._closeQuote; + } else { + this.closePunctuation = this._closeQuote; + } + } + } + + this.string += string; + + // special rule: if a field ends in a punctuation mark, and the suffix + // begins with a period, chop the period off the suffix + var suffix; + if(element && element.suffix && this.format != "compare") { + suffix = element.suffix; // copy so as to leave original intact + + if(suffix[0] == "." && + Zotero.CSL.Compat.FormattedString._punctuation.indexOf(string[string.length-1]) != -1) { + // if string already ends in punctuation, preserve the existing stuff + // and don't add a period + suffix = suffix.substr(1); + } + + this.append(suffix, null, true); + } + + return true; +} + +/* + * gets the formatted string + */ +Zotero.CSL.Compat.FormattedString.prototype.get = function() { + return this.string+(this.closePunctuation ? this.closePunctuation : ""); +} + +/* + * creates a new formatted string with the same formatting parameters as this one + */ +Zotero.CSL.Compat.FormattedString.prototype.clone = function() { + return new Zotero.CSL.Compat.FormattedString(this.CSL, this.format); +} + +/* + * formats a locator (pages, volume, issue) or an identifier (isbn, doi) + * note that label should be null for an identifier + */ +Zotero.CSL.Compat.FormattedString.prototype.appendLocator = function(identifier, number, element) { + if(number) { + var data = this.clone(); + + for(var i in element.children) { + var child = element.children[i]; + var string = ""; + + if(child.name == "number") { + string = number; + } else if(child.name == "text") { + var plural = (identifier && (number.indexOf(",") != -1 + || number.indexOf("-") != -1)); + string = this.CSL._getTerm(child["term-name"], plural, child["form"]); + } else if(identifier && child.name == "label") { + var plural = (number.indexOf(",") != -1 || number.indexOf("-") != -1); + string = this.CSL._getTerm(identifier, plural, child["form"]); + } + + if(string) { + data.append(string, child); + } + } + + this.concat(data, element); + return true; + } else { + return false; + } +} + +/* + * format the date in format supplied by element from the date object + * returned by this._processDate + */ +Zotero.CSL.Compat.FormattedString.prototype.appendDate = function(date, element) { + var data = this.clone(); + if(this.format == "disambiguate") { + // for disambiguation, return only the year + this.append(null, date.year); + return (date.year ? true : false); + } + + var data = this.clone(); + var isData = false; + for(var i in element.children) { + var child = element.children[i]; + var string = ""; + + if(child.name == "year" && date.year) { + if(this.format == "compare") { + string = Zotero.CSL.Compat.Global.lpad(date.year, "0", 4); + } else { + string = date.year.toString(); + if(date.disambiguation) { + string += date.disambiguation; + } + } + } else if(child.name == "month") { + if(date.month != undefined) { + if(this.format == "compare") { + string = Zotero.CSL.Compat.Global.lpad(date.month+1, "0", 2); + } else { + if(element.form == "short") { + string = this.CSL._terms["short"]["_months"][date.month]; + } else { + string = this.CSL._terms["long"]["_months"][date.month]; + } + } + } else if(date.part && this.format != "compare") { + string = date.part; + } + } else if(child.name == "day" && date.day) { + if(this.format == "compare") { + string = Zotero.CSL.Compat.Global.lpad(date.day, "0", 2); + } else { + string = date.day.toString(); + } + } else if(child.name == "text") { + string = this.CSL._getTerm(child["term-name"], false, child["form"]); + } + + if(string) { + data.append(string, child); + isData = true; + } + } + + this.concat(data, element); + + return isData; +} + + +/* + * THE FOLLOWING CODE IS SCHOLAR-SPECIFIC + * gets a list of possible CSL types, in order of preference, for an item + */ + Zotero.CSL.Compat.Global.optionalTypeMappings = { + journalArticle:"article-journal", + magazineArticle:"article-magazine", + newspaperArticle:"article-newspaper", + thesis:"thesis", + letter:"personal communication", + manuscript:"manuscript", + interview:"interview", + film:"motion picture", + artwork:"graphic", + webpage:"webpage", + report:"paper-conference", // ?? + 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:"paper-conference", + videoRecording:"motion picture", + tvBroadcast:"motion picture", + radioBroadcast:"motion picture", + podcast:"speech", // ?? + computerProgram:"book" // ?? +}; +// TODO: check with Elena/APA/MLA on this +Zotero.CSL.Compat.Global.fallbackTypeMappings = { + book:"book", + bookSection:"chapter", + journalArticle:"article", + magazineArticle:"article", + newspaperArticle:"article", + thesis:"book", + letter:"article", + manuscript:"book", + interview:"book", + film:"book", + artwork:"book", + webpage:"article", + report:"book", + bill:"book", + case:"book", + hearing:"book", + patent:"book", + statute:"book", + email:"article", + map:"article", + blogPost:"article", + instantMessage:"article", + forumPost:"article", + audioRecording:"article", + presentation:"article", + videoRecording:"article", + tvBroadcast:"article", + radioBroadcast:"article", + podcast:"article", + computerProgram:"book" +}; + +Zotero.CSL.Compat.prototype._getTypeFromItem = function(item) { + var scholarType = Zotero.ItemTypes.getName(item.getType()); + + // get type + Zotero.debug("CSL: parsing item of Scholar type "+scholarType); + if(Zotero.CSL.Compat.Global.optionalTypeMappings[scholarType]) { // if there is an optional type mapping + var array = [Zotero.CSL.Compat.Global.optionalTypeMappings[scholarType]]; + + // check if there is a fallback type mapping; otherwise, use article + if(Zotero.CSL.Compat.Global.fallbackTypeMappings[scholarType]) { + array.push(Zotero.CSL.Compat.Global.fallbackTypeMappings[scholarType]); + } else { + array.push("article"); + } + + return array; + } else if(Zotero.CSL.Compat.Global.fallbackTypeMappings[scholarType]) { // if there is a fallback type mapping + return [Zotero.CSL.Compat.Global.fallbackTypeMappings[scholarType]]; + } else { // use article as backup type mapping + return ["article"]; + } +} + +/* + * separate creators object into authors, editors, and translators + */ +Zotero.CSL.Compat.prototype._separateItemCreators = function(item) { + var authors = new Array(); + var editors = new Array(); + var translators = new Array(); + + var authorID = Zotero.CreatorTypes.getPrimaryIDForType(item.getType()); + var editorID = Zotero.CreatorTypes.getID("editor"); + var translatorID = Zotero.CreatorTypes.getID("translator"); + + var creators = item.getCreators(); + for each(var creator in creators) { + if(creator.creatorTypeID == editorID) { + editors.push(creator); + } else if(creator.creatorTypeID == translatorID) { + translators.push(creator); + } else if(creator.creatorTypeID == authorID) { + // TODO: do we just ignore contributors? + authors.push(creator); + } + } + + return [authors, editors, translators]; +} + +/* + * return an object containing year, month, and day + */ +Zotero.CSL.Compat.prototype._processDate = function(string) { + return Zotero.Date.strToDate(string); +} + +/* + * get a field on an item + */ +Zotero.CSL.Compat.prototype._getField = function(item, field) { + return item.getField(field, false, true); +} + +/* + * END SCHOLAR-SPECIFIC CODE + */ \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js index 982d2032a..92c09b2da 100644 --- a/chrome/content/zotero/xpcom/integration.js +++ b/chrome/content/zotero/xpcom/integration.js @@ -333,6 +333,13 @@ Zotero.Integration.DataListener.prototype._requestFinished = function(response) } } +Zotero.Integration.citationTypes = { + 1:"first", + 2:"subsequent", + 3:"ibid", + 4:"ibid-pages" +} + Zotero.Integration.SOAP = new function() { this.init = init; this.update = update; @@ -438,7 +445,7 @@ Zotero.Integration.SOAP = new function() { Zotero.debug("Integration: Regenerating bibliography"); // EBNF: bibliography-data if(bibliographyMode != "false") { - output.push(session.style.createBibliography(session.citationFactory.items, "Integration")); + output.push(session.style.createBibliography(session.citationFactory.itemSet, "Integration")); } else { output.push("!"); } @@ -759,9 +766,9 @@ Zotero.Integration.CitationFactory.prototype.updateItems = function(citationSet, } } - this.style.preprocessItems(this.items); + this.itemSet = this.style.generateItemSet(this.items); - var tempCache = new Object(); + /*var tempCache = new Object(); for(var i in this.items) { var itemID = this.items[i].getID(); this.dateModified[itemID] = this.items[i].getField("dateModified"); @@ -788,7 +795,7 @@ Zotero.Integration.CitationFactory.prototype.updateItems = function(citationSet, } } } - } + }*/ // TODO: clear missing items from cache? @@ -810,7 +817,9 @@ Zotero.Integration.CitationFactory.prototype.getCitation = function(citation, us } citation.loadLocators(); - var citationText = this.style.createCitation(citation, "Integration"); + var citationText = this.style.createCitation(this.itemSet, citation.itemIDs, "Integration", + Zotero.Integration.citationTypes[citation.citationType], citation.locators, + citation.locatorTypes); if(!usingCache[citation.serializedType]) { usingCache[citation.serializedType] = new Object(); diff --git a/components/zotero-service.js b/components/zotero-service.js index 12fa70e3c..aed4b8f82 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -58,6 +58,10 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"] .getService(Ci.mozIJSSubScriptLoader) .loadSubScript("chrome://zotero/content/xpcom/cite.js"); +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://zotero/content/xpcom/cite_compat.js"); + Cc["@mozilla.org/moz/jssubscript-loader;1"] .getService(Ci.mozIJSSubScriptLoader) .loadSubScript("chrome://zotero/content/xpcom/quickCopy.js"); diff --git a/scrapers.sql b/scrapers.sql index 31114067d..e5f185012 100644 --- a/scrapers.sql +++ b/scrapers.sql @@ -1,4 +1,4 @@ --- 247 +-- 248 -- ***** BEGIN LICENSE BLOCK ***** -- @@ -22,7 +22,7 @@ -- Set the following timestamp to the most recent scraper update date -REPLACE INTO version VALUES ('repository', STRFTIME('%s', '2007-06-27 02:00:00')); +REPLACE INTO version VALUES ('repository', STRFTIME('%s', '2007-07-14 22:56:53')); REPLACE INTO translators VALUES ('96b9f483-c44d-5784-cdad-ce21b984fe01', '1.0.0b4.r1', '', '2007-06-21 20:00:00', '1', '100', '4', 'Amazon.com', 'Sean Takats', '^https?://(?:www\.)?amazon', 'function detectWeb(doc, url) { @@ -13175,167 +13175,156 @@ function doExport() { } }'); -REPLACE INTO csl VALUES('http://purl.org/net/xbiblio/csl/styles/apa.csl', '2007-06-13 01:00:00', 'American Psychological Association', -' - -'); REPLACE INTO csl VALUES('http://www.zotero.org/namespaces/CSL/chicago-author-date.csl', '2007-04-25 23:40:00', 'Chicago Manual of Style (Author-Date)',