zotero/chrome/chromeFiles/content/scholar/xpcom/cite.js

1077 lines
No EOL
29 KiB
JavaScript

/*
* Scholar.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";
Scholar.Cite = new function() {
this.getBibliography = getBibliography;
this.getStyles = getStyles;
function getStyles() {
// get styles
var sql = "SELECT cslID, title FROM csl ORDER BY title";
var styles = Scholar.DB.query(sql);
// convert to associative array
var stylesObject = new Object();
for each(var style in styles) {
stylesObject[style.cslID] = style.title;
}
return stylesObject;
}
function getBibliography(cslID, items) {
// get style
var sql = "SELECT csl FROM csl WHERE cslID = ?";
var style = Scholar.DB.valueQuery(sql, [cslID]);
// get item arrays
var itemArrays = new Array();
for(var i in items) {
itemArrays.push(items[i].toArray());
}
// create a CSL instance
var cslInstance = new CSL(style);
// return bibliography
return cslInstance.createBibliography(itemArrays, "HTML");
}
}
/*
* 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
*/
CSL = function(csl) {
Scholar.debug(csl);
this._csl = new XML(this._cleanXML(csl));
// initialize CSL
this._init();
// load localizations
this._terms = this._parseTerms(this._csl.terms);
// load class defaults
this._class = this._csl["@class"].toString();
this._defaults = new Object();
// load class defaults
if(CSL._classDefaults[this._class]) {
var classDefaults = CSL._classDefaults[this._class];
for(var i in classDefaults) {
this._defaults[i] = classDefaults[i];
}
}
// load defaults from CSL
this._parseFieldDefaults(this._csl.defaults);
// load options
this._opt = this._parseOptions(this._csl.bibliography);
Scholar.debug(this._opt);
// create an associative array of available types
this._types = new Object();
for each(var type in this._csl.bibliography.layout.item.choose.type) {
this._types[type.@name] = true;
}
}
/*
* create a bibliography
* (items is expected to be an array of items)
*/
CSL.prototype.createBibliography = function(items, format) {
// preprocess items
this._preprocessItems(items);
// sort by sort order
Scholar.debug("sorting items");
var me = this;
items.sort(function(a, b) {
return me._compareItem(a, b);
});
Scholar.debug(items);
// disambiguate items
this._disambiguateItems(items);
// process items
var output = "";
for(var i in items) {
var item = items[i];
if(item.itemType == "note" || item.itemType == "file") {
// skip notes and files
continue;
}
// determine mapping
if(CSL._optionalTypeMappings[item.itemType]
&& this._types[CSL._optionalTypeMappings[item.itemType]]) {
if(this._types[CSL._optionalTypeMappings[item.itemType]] === true) {
// exists but not yet processed
this._parseReferenceType(CSL._optionalTypeMappings[item.itemType]);
}
var typeName = CSL._optionalTypeMappings[item.itemType];
} else {
if(this._types[CSL._fallbackTypeMappings[item.itemType]] === true) {
this._parseReferenceType(CSL._fallbackTypeMappings[item.itemType]);
}
var typeName = CSL._fallbackTypeMappings[item.itemType];
}
var type = this._types[typeName];
var string = "";
for(var i in type) {
string += this._getFieldValue(type[i].name, type[i], item, format, typeName);
}
if(format == "HTML") {
output += '<p style="margin-left:0.5in;text-indent:-0.5in">'+string+'</p>';
}
}
return output;
}
CSL._months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
CSL._monthsShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
CSL._optionalTypeMappings = {
journalArticle:"article-journal",
magazineArticle:"article-magazine",
newspaperArticle:"article-newspaper",
thesis:"thesis",
letter:"personal communication",
manuscript:"manuscript",
interview:"interview",
film:"motion picture",
artwork:"graphic",
website:"webpage"
};
// TODO: check with Elena/APA/MLA on this
CSL._fallbackTypeMappings = {
book:"book",
bookSection:"chapter",
journalArticle:"article",
magazineArticle:"article",
newspaperArticle:"article",
thesis:"book",
letter:"article",
manuscript:"book",
interview:"book",
film:"book",
artwork:"book",
website:"article"
};
// for elements that inherit defaults from each other
CSL._inherit = {
author:"contributor",
editor:"contributor",
translator:"contributor",
pages:"locator",
volume:"locator",
issue:"locator",
isbn:"identifier",
doi:"identifier",
edition:"version"
}
// for class definitions
CSL._classDefaults = new Object();
CSL._classDefaults["author-date"] = {
author:{
substitute:[
{name:"editor"},
{name:"translator"},
{name:"titles", relation:"container", "font-style":"italic"},
{name:"titles", children:[
{name:"title", form:"short"}
]}
]
}
};
CSL.ns = "http://purl.org/net/xbiblio/csl";
CSL.prototype._cleanXML = function(xml) {
return xml.replace(/<\?[^>]*\?>/g, "");
}
CSL.prototype._init = function() {
if(!CSL._xmlLang) {
// get XML lang
var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1'].
getService(Components.interfaces.nsILocaleService);
CSL._xmlLang = localeService.getLocaleComponentForUserAgent();
// read locales.xml from directory
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance();
req.open("GET", "chrome://scholar/locale/locales.xml", false);
req.overrideMimeType("text/plain");
req.send(null);
// get default terms
var terms = new XML(this._cleanXML(req.responseText));
CSL._defaultTerms = this._parseTerms(terms);
}
}
CSL.prototype._parseTerms = function(termXML) {
// return defaults if there are no terms
if(!termXML.length()) {
return (CSL._defaultTerms ? CSL._defaultTerms : {});
}
var xml = new Namespace("http://www.w3.org/XML/1998/namespace");
// get proper locale
var locale = termXML.locale.(@xml::lang == CSL._xmlLang);
if(!locale.length()) {
var xmlLang = CSL._xmlLang.substr(0, 2);
locale = termXML.locale.(@xml::lang == xmlLang);
}
if(!locale.length()) {
// return defaults if there are no locales
return (CSL._defaultTerms ? CSL._defaultTerms : {});
}
var termArray = new Array();
if(CSL._defaultTerms) {
// ugh. copy default array. javascript dumb.
for(var i in CSL._defaultTerms) {
if(typeof(CSL._defaultTerms[i]) == "object") {
termArray[i] = [CSL._defaultTerms[i][0],
CSL._defaultTerms[i][1]];
} else {
termArray[i] = CSL._defaultTerms[i];
}
}
}
// 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");
}
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[name]) { // no previously defined value
termArray[name] = [single, multiple];
} else {
if(typeof(termArray[name]) != "object") {
termArray[name] = [termArray[name], termArray[name]];
}
// redefine either single or multiple
if(single) {
termArray[name][0] = single;
} else {
termArray[name][1] = multiple;
}
}
} else {
termArray[name] = term.text().toString();
}
}
return termArray;
}
/*
* parses attributes and children for a CSL field
*/
CSL.prototype._parseFieldAttrChildren = function(element, desc) {
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(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() == CSL.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() == CSL.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
*/
CSL.prototype._parseFieldDefaults = function(ref) {
for each(var element in ref.children()) {
if(element.namespace() == CSL.ns) { // ignore elements in other namespaces
var name = element.localName();
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
*/
CSL.prototype._parseFields = function(ref, useDefaults) {
var typeDesc = new Array();
for each(var element in ref) {
if(element.namespace() == CSL.ns) { // ignore elements in other namespaces
var itemDesc = new Object();
itemDesc.name = element.localName();
// parse attributes on this field
this._parseFieldAttrChildren(element, itemDesc);
// add defaults
if(useDefaults) {
var fieldDefaults = this._getFieldDefaults(itemDesc.name);
itemDesc = this._merge(fieldDefaults, itemDesc);
itemDesc = this._merge(this._opt.format, itemDesc);
}
// parse group children
if(itemDesc.name == "group" && itemDesc.children) {
for(var i in itemDesc.children) {
itemDesc.children[i] = this._merge(this._getFieldDefaults(itemDesc.children[i].name), itemDesc.children[i]);
}
}
typeDesc.push(itemDesc);
}
}
return typeDesc;
}
/*
* parses cs-format attributes into just a prefix and a suffix; accepts an
* optional array of cs-format
*/
CSL.prototype._parseOptions = function(bibliography) {
var opt = new Object();
// subsequent author substitute
// replaces subsequent occurances of an author with a given string
if(bibliography['@subsequent-author-substitute']) {
opt.subsequentAuthorSubstitute = bibliography['@subsequent-author-substitute'].toString();
}
// hanging indent
if(bibliography['@hanging-indent']) {
opt.hangingIndent = true;
}
// author as sort order
// for our purposes, this controls whether an author is last, first or
// first last (although for internationalized names it means something
// different)
if(bibliography['@author-as-sort-order']) {
if(bibliography['@author-as-sort-order'] == "first-author") {
opt.authorAsSortOrder = "first-author";
} else if(bibliography['@author-as-sort-order'] == "all") {
opt.authorAsSortOrder = "all";
}
}
// sort order
var algorithm = bibliography.sort.@algorithm.toString();
if(algorithm) {
// for classes, use the sort order that
if(algorithm == "author-date") {
opt.sortOrder = [this._getFieldDefaults("author"),
this._getFieldDefaults("date")];
opt.sortOrder[0].name = "author";
opt.sortOrder[1].name = "date";
} else if(algorithm == "label") {
opt.sortOrder = [this._getFieldDefaults("label")];
opt.sortOrder[0].name = "label";
} else if(algorithm == "cited") {
opt.sortOrder = [this._getFieldDefaults("cited")];
opt.sortOrder[0].name = "cited";
}
} else {
opt.sortOrder = this._parseFields(bibliography.sort, false);
}
// et al
if(bibliography['use-et_al'].length()) {
opt.etAl = new Object();
opt.etAl.minCreators = parseInt(bibliography['use-et_al']['@min-authors']);
opt.etAl.useFirst = parseInt(bibliography['use-et_al']['@use-first']);
}
// sections (TODO)
opt.sections = [{groupBy:"default",
heading:bibliography.layout.heading.text["@term-name"].toString()}];
for each(var section in bibliography.layout.section) {
opt.sections.push([{groupBy:section["@group-by"].toString(),
heading:section.heading.text["@term-name"].toString()}]);
}
// global prefix and suffix format information
opt.format = new Array();
for each(var attribute in bibliography.layout.item.attributes()) {
opt.format[attribute.name()] = attribute.toString();
}
return opt;
}
/*
* convert reference types to native structures for speed
*/
CSL.prototype._parseReferenceType = function(reftype) {
var ref = this._csl.bibliography.layout.item.choose.type.(@name==reftype).children();
this._types[reftype] = this._parseFields(ref, true);
}
/*
* merges two elements, letting the second override the first
*/
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)
*/
CSL.prototype._getFieldDefaults = function(elementName) {
// first, see if there are specific defaults
if(this._defaults[elementName]) {
if(CSL._inherit[elementName]) {
var inheritedDefaults = this._getFieldDefaults(CSL._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(CSL._inherit[elementName]) {
return this._getFieldDefaults(CSL._inherit[elementName]);
}
// finally, return an empty object
return {};
}
/*
* gets a term, in singular or plural form
*/
CSL.prototype._getTerm = function(term, plural) {
if(!this._terms[term]) {
return "";
}
if(typeof(this._terms[term]) == "object") { // singular and plural forms
// are available
if(plural) {
return this._terms[term][1];
} else {
return this._terms[term][0];
}
}
return this._terms[term];
}
/*
* process the date "string" into a useful object
*/
CSL.prototype._processDate = function(string) {
var date = new Object();
var dateRe = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
var m = dateRe.exec(string);
if(m) { // sql date
var jsDate = new Date(m[1], m[2]-1, m[3], false, false, false);
} else { // not an sql date
var jsDate = new Date(string);
}
if(isNaN(jsDate.valueOf())) { // couldn't parse
// get year and say other parts are month
var yearRe = /^(.*)([^0-9]{4})(.*)$/
var m = yearRe.exec(string);
date.year = m[2];
date.month = m[1]
if(m[2] && m[3]) date.month += " ";
date.month += m[3];
} else {
date.year = jsDate.getFullYear();
date.month = jsDate.getMonth();
date.day = jsDate.getDay();
}
return date;
}
/*
* formats a string according to the cs-format attributes on element
*/
CSL.prototype._formatString = function(element, string, format) {
if(format != "compare" && element.prefix) {
string = element.prefix+string;
}
if(format == "HTML") {
var style = "";
var cssAttributes = ["font-family", "font-style", "font-variant",
"font-weight", "text-transform"];
for(var j in cssAttributes) {
if(element[cssAttributes[j]] && element[cssAttributes[j]].indexOf('"') == -1) {
style += cssAttributes[j]+":"+element[cssAttributes[j]];
}
}
if(style) {
string = '<span style="'+style+'">'+string+'</span>';
}
}
if(format != "compare" && element.suffix &&
(element.suffix.length != 1 || string[string.length-1] != element.suffix)) {
// skip if suffix is the same as the last char
string += element.suffix;
}
return string;
}
/*
* formats a locator (pages, volume, issue)
*/
CSL.prototype._formatLocator = function(element, number, format) {
var data = "";
if(number) {
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 = (item.pages.indexOf(",") != -1 || item.pages.indexOf("-") != -1);
string = this._getTerm(child["term-name"], plural);
}
if(string) {
data += this._formatString(child, string, format);
}
}
}
return data;
}
/*
* format the date in format supplied by element from the date object
* returned by this._processDate
*/
CSL.prototype._formatDate = function(element, date, format) {
if(format == "disambiguate") {
// for disambiguation, return only the year
return date.year;
}
var data = "";
for(var i in element.children) {
var child = element.children[i];
var string = "";
if(child.name == "year" && date.year) {
if(format == "compare") {
string = this._lpad(date.year, "0", 4);
} else {
string = date.year.toString();
if(date.disambiguation) {
string += date.disambiguation;
}
}
} else if(child.name == "month" && date.month) {
if(format == "compare") {
string = this._lpad(date.month+1, "0", 2);
} else {
if(element.form == "short") {
string = CSL._monthsShort[date.month];
} else {
string = CSL._months[date.month];
}
}
} else if(child.name == "day" && date.day) {
if(format == "compare") {
string = this._lpad(date.day, "0", 2);
} else {
string = date.day.toString();
}
}
if(string) {
data += this._formatString(child, string, format);
}
}
return data;
}
/*
* pads a number or other string with a given string on the left
*/
CSL.prototype._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
*/
CSL.prototype._preprocessItems = function(items) {
for(var i in items) {
var item = items[i];
// namespace everything in item._csl so there's no chance of overlap
item._csl = new Object();
item._csl.ignore = new Array();
item._csl.authors = new Array();
item._csl.editors = new Array();
item._csl.translators = new Array();
// separate item into authors, editors, translators
for(var j in item.creators) {
var creator = item.creators[j];
if(creator.creatorType == "editor") {
item._csl.editors.push(creator);
} else if(creator.creatorType == "translator") {
item._csl.translators.push(creator);
} else if(creator.creatorType == "author") {
// TODO: do we just ignore contributors?
item._csl.authors.push(creator);
}
}
// parse
if(item.date) { // specific date
item._csl.date = CSL.prototype._processDate(item.date);
} else { // no real date, but might salvage a year
item._csl.date = new Object();
if(item.year) {
item._csl.date.year = item.year;
}
}
}
}
/*
* disambiguates items, after pre-processing and sorting
*/
CSL.prototype._disambiguateItems = function(items) {
var usedCitations = new Array();
var lastAuthor;
for(var i in items) {
var item = items[i];
var author = this._getFieldValue("author",
this._getFieldDefaults("author"),
item, "disambiguate");
// handle (2006a) disambiguation for author-date styles
if(this._class == "author-date") {
var citation = author+" "+this._getFieldValue("date",
this._getFieldDefaults("date"),
item, "disambiguate");
Scholar.debug(citation);
if(usedCitations[citation]) {
Scholar.debug("disambiguation necessary");
if(!usedCitations[citation]._csl.date.disambiguation) {
usedCitations[citation]._csl.date.disambiguation = "a";
item._csl.date.disambiguation = "b";
} else {
// get all but last character
var oldLetter = usedCitations[citation]._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);
}
}
}
usedCitations[citation] = item;
}
// handle subsequent author substitutes
if(this._opt.subsequentAuthorSubstitute && lastAuthor == author) {
item._csl.subsequentAuthorSubstitute = true;
}
lastAuthor = author;
}
}
/*
* handles sorting of items
*/
CSL.prototype._compareItem = function(a, b, opt) {
for(var i in this._opt.sortOrder) {
var sortElement = this._opt.sortOrder[i];
var aValue = this._getFieldValue(sortElement.name, sortElement, a, "compare");
var bValue = this._getFieldValue(sortElement.name, sortElement, b, "compare");
if(bValue > aValue) {
return -1;
} else if(bValue < aValue) {
return 1;
}
}
// 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
*/
CSL.prototype._processCreators = function(type, element, creators, format) {
var maxCreators = creators.length;
if(!maxCreators) return;
if(format == "disambiguate") {
// for disambiguation, return only the last name of the first creator
return creators[0].lastName;;
}
var data = "";
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(this._opt.etAl && maxCreators >= this._opt.etAl.minCreators) {
maxCreators = this._opt.etAl.useFirst;
useEtAl = true;
}
// parse authors into strings
var authorStrings = [];
var firstName, lastName;
for(var i=0; i<maxCreators; i++) {
if(child["initialize-with"]) {
// use firist initials
var firstName = "";
var firstNames = creators[i].firstName.split(" ");
for(var j in firstNames) {
if(firstNames[j]) {
// get first initial, put in upper case, add initializeWith string
firstName += firstNames[j][0].toUpperCase()+child["initialize-with"];
}
}
} else {
firstName = creators[i].firstName;
}
lastName = creators[i].lastName;
if(((i == 0 && this._opt.authorAsSortOrder == "first-author")
|| this._opt.authorAsSortOrder == "all")
&& child["sort-separator"]) {
// if this is the first author and author-as-sort-order="first-author"
// or if this is a subsequent author and author-as-sort-order="all"
// then the name gets inverted
authorStrings.push(lastName+child["sort-separator"]+firstName);
} else {
authorStrings.push(firstName+" "+lastName);
}
}
// figure out if we need an "and" or an "et al"
var joinString = ", ";
if(maxCreators > 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 {
var and = this._getTerm("and");
}
authorStrings[maxCreators-1] = and+" "+authorStrings[maxCreators-1];
// skip the comma if there are only two creators and no
// et al
if(maxCreators == 2) {
joinString = " ";
}
}
}
}
string = authorStrings.join(joinString);
} else if(child.name == "role") {
string = this._getTerm(type, (maxCreators != 1));
}
// add string to data
if(string) {
data += this._formatString(child, string, format);
}
}
// add to the data
return data;
}
/*
* processes an element from a (pre-processed) item into text
*/
CSL.prototype._getFieldValue = function(name, element, item, format, typeName) {
var data = "";
// make sure we're not supposed to ignore this (bc it's already substituted)
for(var i in item._csl.ignore) {
if(item._csl.ignore[i] == element) {
return "";
}
}
if(name == "author") {
if(item._csl.subsequentAuthorSubstitute) {
// handle subsequent author substitute behavior
data = this._opt.subsequentAuthorSubstitute;
} else {
data = this._processCreators(name, element, item._csl.authors, format);
}
} else if(name == "editor") {
data = this._processCreators(name, element, item._csl.editors, format);
} else if(name == "translator") {
data = this._processCreators(name, element, item._csl.translators, format);
} else if(name == "titles") {
for(var i in element.children) {
var child = element.children[i];
var string = "";
if(child.name == "title") { // for now, we only care about the
// "title" sub-element
if(!element.relation) {
string = item.title;
} else if(element.relation == "container") {
string = item.publicationTitle;
} else if(element.relation == "collection") {
string = item.seriesTitle;
}
}
if(string) {
data += this._formatString(child, string, format);
}
}
} else if(name == "date") {
data = this._formatDate(element, item._csl.date, format);
} else if(name == "publisher") {
for(var i in element.children) {
var child = element.children[i];
var string = "";
if(child.name == "place") {
string = item.place;
} else if(child.name == "name") {
string = item.publisher
}
if(string) {
data += this._formatString(child, string, format);
}
}
} else if(name == "access") {
var save = false;
for(var i in element.children) {
var child = element.children[i];
var string = "";
if(child.name == "url") {
// TODO: better URL-handling strategies
if(item.url) {
string = item.url;
}
} else if(child.name == "date") {
string = this._formatDate(child, this._processDate(item.accessDate), format);
} else if(child.name == "physicalLocation") {
string = item.archiveLocation;
} else if(child.name == "text") {
string = this._getTerm(child["term-name"]);
}
if(string) {
data += this._formatString(child, string, format);
if(child.name != "text") {
// only save if there's non-text data
save = true;
}
}
}
if(!save) {
data = "";
}
} else if(name == "volume") {
data = this._formatLocator(element, item.volume, format);
} else if(name == "issue") {
data = this._formatLocator(element, item.issue, format);
} else if(name == "pages") {
data = this._formatLocator(element, item.pages, format);
} else if(name == "edition") {
data = item.edition;
} else if(name == "genre") {
data = (item.type ? item.type : item.thesisType);
} else if(name == "group") {
for(var i in element.children) {
var child = element.children[i];
data += this._getFieldValue(child.name, child, item, format, typeName);
}
} else if(name == "text") {
string = this._getTerm(child["term-name"]);
} else if(name == "isbn") {
data = this._formatLocator(element, item.ISBN, format);
} else if(name == "doi") {
data = this._formatLocator(element, item.DOI, format);
} else {
data = name;
}
if(data) {
return this._formatString(element, data, format);
} else if(element.substitute) {
// try each substitute element until one returns something
for(var i in element.substitute) {
var substituteElement = element.substitute[i];
var defaultElement;
// first try to get from the type
if(typeName && this._types[typeName]) {
for(var i in this._types[typeName]) {
var field = this._types[typeName][i];
if(field.name == substituteElement.name
&& (!substituteElement.relation
|| field.relation == substituteElement.relation)
&& (!substituteElement.role
|| field.role == substituteElement.role)) {
defaultElement = field;
// flag to be ignored later
item._csl.ignore.push(defaultElement);
}
}
}
// otherwise, get default
if(!defaultElement) {
defaultElement = this._getFieldDefaults(substituteElement.name);
}
// copy prefix and suffix
substituteElement.prefix = element.prefix;
substituteElement.suffix = element.suffix;
data = this._getFieldValue(substituteElement.name,
this._merge(defaultElement, substituteElement), item, format);
if(data) {
return data;
}
}
}
return "";
}