From c1c9673dc6e5e7dca0687c12ede293ea64876e7c Mon Sep 17 00:00:00 2001
From: Simon Kornblith <simon@simonster.com>
Date: Thu, 5 May 2011 18:12:27 +0000
Subject: [PATCH] update to citeproc-js 1.0.163

---
 chrome/content/zotero/xpcom/citeproc.js | 514 +++++++++++++-----------
 1 file changed, 284 insertions(+), 230 deletions(-)

diff --git a/chrome/content/zotero/xpcom/citeproc.js b/chrome/content/zotero/xpcom/citeproc.js
index 86def9b96d..6cbd935dde 100644
--- a/chrome/content/zotero/xpcom/citeproc.js
+++ b/chrome/content/zotero/xpcom/citeproc.js
@@ -141,13 +141,14 @@ var CSL = {
 	QUOTED_REGEXP_END: /^"$/,
 	NAME_INITIAL_REGEXP: /^([A-Z\u0080-\u017f\u0400-\u042f])([a-zA-Z\u0080-\u017f\u0400-\u052f]*|)/,
 	ROMANESQUE_REGEXP: /[a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe]/,
+	ROMANESQUE_NOT_REGEXP: /[^a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe]/g,
 	STARTSWITH_ROMANESQUE_REGEXP: /^[&a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe]/,
 	ENDSWITH_ROMANESQUE_REGEXP: /[.;:&a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe]$/,
 	ALL_ROMANESQUE_REGEXP: /^[a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe]+$/,
 	VIETNAMESE_SPECIALS: /[\u00c0-\u00c3\u00c8-\u00ca\u00cc\u00cd\u00d2-\u00d5\u00d9\u00da\u00dd\u00e0-\u00e3\u00e8-\u00ea\u00ec\u00ed\u00f2-\u00f5\u00f9\u00fa\u00fd\u0101\u0103\u0110\u0111\u0128\u0129\u0168\u0169\u01a0\u01a1\u01af\u01b0\u1ea0-\u1ef9]/,
 	VIETNAMESE_NAMES: /^(?:(?:[.AaBbCcDdEeGgHhIiKkLlMmNnOoPpQqRrSsTtUuVvXxYy \u00c0-\u00c3\u00c8-\u00ca\u00cc\u00cd\u00d2-\u00d5\u00d9\u00da\u00dd\u00e0-\u00e3\u00e8-\u00ea\u00ec\u00ed\u00f2-\u00f5\u00f9\u00fa\u00fd\u0101\u0103\u0110\u0111\u0128\u0129\u0168\u0169\u01a0\u01a1\u01af\u01b0\u1ea0-\u1ef9]{2,6})(\s+|$))+$/,
 	NOTE_FIELDS_REGEXP: /\{:[\-a-z]+:[^\}]+\}/g,
-	NOTE_FIELD_REGEXP: /\{:([\-a-z]+):([^\}]+)\}/,
+	NOTE_FIELD_REGEXP: /\{:([\-a-z]+):\s*([^\}]+)\}/,
 	DISPLAY_CLASSES: ["block", "left-margin", "right-inline", "indent"],
 	NAME_VARIABLES: [
 		"author",
@@ -527,12 +528,15 @@ CSL.Output.Queue.prototype.pop = function () {
 	return this.current.value().blobs.pop();
 };
 CSL.Output.Queue.prototype.getToken = function (name) {
+	CSL.debug("XXX loc [1]");
 	var ret = this.formats.value()[name];
 	return ret;
 };
 CSL.Output.Queue.prototype.mergeTokenStrings = function (base, modifier) {
 	var base_token, modifier_token, ret, key;
+	CSL.debug("XXX loc [2]");
 	base_token = this.formats.value()[base];
+	CSL.debug("XXX loc [3]");
 	modifier_token = this.formats.value()[modifier];
 	ret = base_token;
 	if (modifier_token) {
@@ -560,6 +564,7 @@ CSL.Output.Queue.prototype.addToken = function (name, modifier, token) {
 	var newtok, attr;
 	newtok = new CSL.Token("output");
 	if ("string" === typeof token) {
+	CSL.debug("XXX loc [4]");
 		token = this.formats.value()[token];
 	}
 	if (token && token.strings) {
@@ -573,16 +578,23 @@ CSL.Output.Queue.prototype.addToken = function (name, modifier, token) {
 	if ("string" === typeof modifier) {
 		newtok.strings.delimiter = modifier;
 	}
+	CSL.debug("XXX loc [5]");
 	this.formats.value()[name] = newtok;
 };
+var TESTINGTHING = {};
+TESTINGTHING.counter = 0;
 CSL.Output.Queue.prototype.pushFormats = function (tokenstore) {
 	if (!tokenstore) {
 		tokenstore = {};
 	}
+	CSL.debug("XXX pushFormats() ["+TESTINGTHING.counter+"]");
+	TESTINGTHING.counter += 1;
 	tokenstore.empty = this.empty;
 	this.formats.push(tokenstore);
 };
 CSL.Output.Queue.prototype.popFormats = function (tokenstore) {
+	TESTINGTHING.counter += 1;
+	CSL.debug("XXX popFormats() ["+TESTINGTHING.counter+"]");
 	this.formats.pop();
 };
 CSL.Output.Queue.prototype.startTag = function (name, token) {
@@ -600,34 +612,17 @@ CSL.Output.Queue.prototype.openLevel = function (token, ephemeral) {
 	if ("object" === typeof token) {
 		blob = new CSL.Blob(token);
 	} else if ("undefined" === typeof token) {
+	CSL.debug("XXX loc [6]");
 		blob = new CSL.Blob(this.formats.value().empty, false, "empty");
 	} else {
-		if (!this.formats.value()[token]) {
+		CSL.debug("XXX loc [7]");
+		if (!this.formats.value() || !this.formats.value()[token]) {
 			throw "CSL processor error: call to nonexistent format token \"" + token + "\"";
 		}
+	CSL.debug("XXX loc [8]");
 		blob = new CSL.Blob(this.formats.value()[token], false, token);
 	}
-	if (this.state.tmp.count_offset_characters && blob.strings.prefix.length) {
-		this.state.tmp.offset_characters += blob.strings.prefix.length;
-	}
-	if (this.state.tmp.count_offset_characters && blob.strings.suffix.length) {
-		this.state.tmp.offset_characters += blob.strings.suffix.length;
-	}
 	curr = this.current.value();
-	has_ephemeral = false;
-	for (x in this.state.tmp.names_cut.variable) {
-		if (this.state.tmp.names_cut.variable.hasOwnProperty(x)) {
-			has_ephemeral = x;
-			break;
-		}
-	}
-	if (ephemeral && (!has_ephemeral || ephemeral === has_ephemeral)) {
-		if (!this.state.tmp.names_cut.variable[ephemeral]) {
-			this.state.tmp.names_cut.variable[ephemeral] = [];
-			this.state.tmp.names_cut.used = ephemeral;
-		}
-		this.state.tmp.names_cut.variable[ephemeral].push([curr, curr.blobs.length]);
-	}
 	curr.push(blob);
 	this.current.push(blob);
 };
@@ -653,11 +648,13 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious) {
 	}
 	blob = false;
 	if (!tokname) {
+	CSL.debug("XXX loc [9]");
 		token = this.formats.value().empty;
 	} else if (tokname === "literal") {
 		token = true;
 		useblob = false;
 	} else if ("string" === typeof tokname) {
+	CSL.debug("XXX loc [10]");
 		token = this.formats.value()[tokname];
 	} else {
 		token = tokname;
@@ -669,33 +666,16 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious) {
 		token.strings.delimiter = "";
 	}
 	if ("string" === typeof str && str.length) {
-		str = str.replace(/ ([:;?!\u00bb])/g, "\u202f$1").replace(/\u00ab /g, "«\u202f");
+		str = str.replace(/ ([:;?!\u00bb])/g, "\u202f$1").replace(/\u00ab /g, "\u00ab\u202f");
 		this.last_char_rendered = str.slice(-1);
 		str = str.replace(/\s+'/g, "  \'").replace(/^'/g, " \'");
 		this.state.tmp.term_predecessor = true;
 	}
 	blob = new CSL.Blob(token, str);
-	if (this.state.tmp.count_offset_characters && blob.strings.prefix) {
-		this.state.tmp.offset_characters += blob.strings.prefix.length;
-	}
-	if (this.state.tmp.count_offset_characters && blob.strings.suffix) {
-		this.state.tmp.offset_characters += blob.strings.suffix.length;
-	}
 	curr = this.current.value();
 	if ("string" === typeof blob.blobs) {
 		this.state.tmp.term_predecessor = true;
 	}
-	if (this.state.tmp.count_offset_characters) {
-		if ("string" === typeof str) {
-			this.state.tmp.offset_characters += blob.strings.prefix.length;
-			this.state.tmp.offset_characters += blob.strings.suffix.length;
-			this.state.tmp.offset_characters += blob.blobs.length;
-		} else if ("undefined" !== str.num) {
-			this.state.tmp.offset_characters += str.strings.prefix.length;
-			this.state.tmp.offset_characters += str.strings.suffix.length;
-			this.state.tmp.offset_characters += str.formatter.format(str.num).length;
-		}
-	}
 	if (!notSerious) {
 		this.state.parallel.AppendBlobPointer(curr);
 	}
@@ -724,6 +704,9 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
 	var blob_delimiter = "";
 	if (blob) {
 		blob_delimiter = blob.strings.delimiter;
+	} else {
+		state.tmp.count_offset_characters = false;
+		state.tmp.offset_characters = 0;
 	}
 	if (blob && blob.new_locale) {
 		state.opt.lang = blob.new_locale;
@@ -731,13 +714,15 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
 	var blobjr, use_suffix, use_prefix, params;
 	for (i = 0, ilen = blobs.length; i < ilen; i += 1) {
 		blobjr = blobs[i];
+		if (blobjr.strings.first_blob) {
+			state.tmp.count_offset_characters = blobjr.strings.first_blob;
+		}
 		if ("string" === typeof blobjr.blobs) {
 			if ("number" === typeof blobjr.num) {
 				ret.push(blobjr);
 			} else if (blobjr.blobs) {
 				b = blobjr.blobs;
-				use_suffix = blobjr.strings.suffix;
-				use_prefix = blobjr.strings.prefix;
+				var blen = b.length;
 				if (!state.tmp.suppress_decorations) {
 					for (j = 0, jlen = blobjr.decorations.length; j < jlen; j += 1) {
 						params = blobjr.decorations[j];
@@ -748,15 +733,20 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
 					}
 				}
 				if (b && b.length) {
-					b = txt_esc(blobjr.strings.prefix) + b + txt_esc(use_suffix);
+					b = txt_esc(blobjr.strings.prefix) + b + txt_esc(blobjr.strings.suffix);
 					ret.push(b);
+					if (state.tmp.count_offset_characters) {
+						state.tmp.offset_characters += (blen + blobjr.strings.suffix.length + blobjr.strings.prefix.length);
+					}
 				}
 			}
 		} else if (blobjr.blobs.length) {
 			var addtoret = state.output.string(state, blobjr.blobs, blobjr);
 			ret = ret.concat(addtoret);
-		} else {
-			continue;
+		}
+		if (blobjr.strings.first_blob) {
+			state.registry.registry[state.tmp.count_offset_characters].offset = state.tmp.offset_characters;
+			state.tmp.count_offset_characters = false;
 		}
 	}
 	var span_split = 0;
@@ -768,6 +758,13 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
 	if (blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
 		span_split = ret.length;
 	}
+	var mytype = "string";
+	for (var q = 0, qlen = ret.length; q < qlen; q += 1) {
+		if (typeof ret[q] !== "string") {
+			mytype = typeof ret[q];
+			break;
+		}
+	}
 	var blobs_start = state.output.renderBlobs(ret.slice(0, span_split), blob_delimiter);
 	if (blobs_start && blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
 		if (!state.tmp.suppress_decorations) {
@@ -784,6 +781,9 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
 		if (b && b.length) {
 			use_prefix = blob.strings.prefix;
 			b = txt_esc(use_prefix) + b + txt_esc(use_suffix);
+			if (state.tmp.count_offset_characters) {
+				state.tmp.offset_characters += (use_prefix.length + use_suffix.length);
+			}
 		}
 		blobs_start = b;
 		if (!state.tmp.suppress_decorations) {
@@ -869,8 +869,12 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim) {
 		if (blob && "string" === typeof blob) {
 			ret += txt_esc(use_delim);
 			ret += blob;
+			if (state.tmp.count_offset_characters) {
+				state.tmp.offset_characters += (use_delim.length);
+			}
 		} else if (blob.status !== CSL.SUPPRESS) {
 			str = blob.formatter.format(blob.num, blob.gender);
+			var strlen = str.length;
 			if (blob.strings["text-case"]) {
 				str = CSL.Output.Formatters[blob.strings["text-case"]](this.state, str);
 			}
@@ -885,16 +889,21 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim) {
 				}
 			}
 			str = blob.strings.prefix + str + blob.strings.suffix;
+			var addme = "";
 			if (blob.status === CSL.END) {
-				ret += blob.range_prefix;
+				addme = blob.range_prefix;
 			} else if (blob.status === CSL.SUCCESSOR) {
-				ret += blob.successor_prefix;
+				addme = blob.successor_prefix;
 			} else if (blob.status === CSL.START) {
-				ret += "";
+				addme = "";
 			} else if (blob.status === CSL.SEEN) {
-				ret += blob.splice_prefix;
+				addme = blob.splice_prefix;
 			}
+			ret += addme;
 			ret += str;
+			if (state.tmp.count_offset_characters) {
+				state.tmp.offset_characters += (addme.length + blob.strings.prefix.length + strlen + blob.strings.suffix.length);
+			}
 		}
 	}
 	return ret;
@@ -1464,7 +1473,7 @@ CSL.XmlToToken = function (state, tokentype) {
 	target = state[state.build.area].tokens;
 	CSL.Node[name].build.call(token, state, target);
 };
-CSL.DateParser = function (txt) {
+CSL.DateParser = function () {
 	var jiy_list, jiy, jiysplitter, jy, jmd, jr, pos, key, val, yearlast, yearfirst, number, rangesep, fuzzychar, chars, rex, rexdash, rexdashslash, rexslashdash, seasonstrs, seasonrexes, seasonstr, monthstrs, monthstr, mrexes, seasonrex, len, jiymatchstring, jiymatcher;
 	jiy_list = [
 		["\u660E\u6CBB", 1867],
@@ -1831,7 +1840,7 @@ CSL.DateParser = function (txt) {
 };
 CSL.Engine = function (sys, style, lang, forceLang) {
 	var attrs, langspec, localexml, locale;
-	this.processor_version = "1.0.155";
+	this.processor_version = "1.0.163";
 	this.csl_version = "1.0";
 	this.sys = sys;
 	this.sys.xml = new CSL.System.Xml.Parsing();
@@ -1868,7 +1877,6 @@ CSL.Engine = function (sys, style, lang, forceLang) {
 	}
 	this.opt["initialize-with-hyphen"] = true;
 	this.setStyleAttributes();
-	CSL.Util.Names.initNameSlices(this);
 	this.opt.xclass = sys.xml.getAttributeValue(this.cslXml, "class");
 	if (lang) {
 		lang = lang.replace("_", "-");
@@ -2007,6 +2015,10 @@ CSL.Engine.prototype.getNavi.prototype.getNodeListValue = function () {
 	return this.nodeList[this.depth][1];
 };
 CSL.Engine.prototype.getTerm = function (term, form, plural, gender, loose) {
+	if (term && term.match(/[A-Z]/) && term === term.toUpperCase()) {
+		CSL.debug("Warning: term key is in uppercase form: "+term);
+		term = term.toLowerCase();
+	}
 	var ret = CSL.Engine.getField(CSL.LOOSE, this.locale[this.opt.lang].terms, term, form, plural, gender);
 	if (typeof ret === "undefined") {
 		ret = CSL.Engine.getField(CSL.STRICT, this.locale[this.opt.lang].terms, term, form, plural, gender);
@@ -2136,7 +2148,11 @@ CSL.Engine.prototype.retrieveItem = function (id) {
 			for (pos = 0, len = m.length; pos < len; pos += 1) {
 				mm = CSL.NOTE_FIELD_REGEXP.exec(m[pos]);
 				if (!Item[mm[1]]) {
-					Item[mm[1]] = mm[2].replace(/^\s+/, "").replace(/\s+$/, "");
+					if (CSL.DATE_VARIABLES.indexOf(mm[1]) > -1) {
+						Item[mm[1]] = {raw:mm[2]};
+					} else {
+						Item[mm[1]] = mm[2].replace(/^\s+/, "").replace(/\s+$/, "");
+					}
 				}
 			}
 		}
@@ -2273,6 +2289,7 @@ CSL.Engine.Opt = function () {
 	this["parse-names"] = true;
 	this.citation_number_slug = false;
 	this.max_number_of_names = 0;
+	this.trigraph = "Aaaa00:AaAa00:AaAA00:AAAA00";
 };
 CSL.Engine.Tmp = function () {
 	this.names_max = new CSL.Stack();
@@ -2307,7 +2324,6 @@ CSL.Engine.Tmp = function () {
 	this.prefix = new CSL.Stack("", CSL.LITERAL);
 	this.suffix = new CSL.Stack("", CSL.LITERAL);
 	this.delimiter = new CSL.Stack("", CSL.LITERAL);
-	this.names_cut = {};
 	this.cite_locales = [];
 	this.cite_affixes = false;
 };
@@ -2497,6 +2513,91 @@ CSL.Engine.prototype.updateUncitedItems = function (idList, nosort) {
 	this.registry.renumber();
 	return this.registry.getSortedIds();
 };
+CSL.Engine.prototype.getCitationLabel = function (Item) {
+	var label = "";
+	var params = this.getTrigraphParams();
+	var config = params[0];
+	var myname = this.getTerm("reference", "short", 0);
+	myname = myname.replace(".", "");
+	myname = myname.slice(0, 1).toUpperCase() + myname.slice(1);
+	for (var i = 0, ilen = CSL.CREATORS.length; i < ilen; i += 1) {
+		var n = CSL.CREATORS[i];
+		if (Item[n]) {
+			var names = Item[n];
+			if (names.length > params.length) {
+				config = params[params.length - 1];
+			} else {
+				config = params[names.length - 1];
+			}
+			for (var j = 0, jlen = names.length; j < jlen; j += 1) {
+				if (j === config.authors.length) {
+					break;
+				}
+				var name = this.transform.name(this, names[j], this.opt["locale-pri"]);
+				if (name && name.family) {
+					myname = name.family;
+					myname = myname.replace(/^([ \'\u2019a-z]+\s+)/, "");
+				} else if (name && name.literal) {
+					myname = name.literal;
+				}
+				var m = myname.toLowerCase().match(/^(a\s+|the\s+|an\s+)/);
+				if (m) {
+					myname = myname.slice(m[1].length);
+				}
+				myname = myname.replace(CSL.ROMANESQUE_NOT_REGEXP, "", "g");
+				if (!myname) {
+					break;
+				}
+				myname = myname.slice(0, config.authors[j]);
+				if (myname.length > 1) {
+					myname = myname.slice(0, 1).toUpperCase() + myname.slice(1).toLowerCase();
+				} else if (myname.length === 1) {
+					myname = myname.toUpperCase();
+				}
+				label += myname;
+			}
+			break;
+		}
+	}
+	var year = "0000";
+	if (Item.issued) {
+		var dp = Item.issued["date-parts"];
+		if (dp && dp[0] && dp[0][0]) {
+			year = "" + dp[0][0];
+		}
+	}
+	year = year.slice((config.year * -1));
+	label = label + year;
+	return label;
+};
+CSL.Engine.prototype.getTrigraphParams = function () {
+	var params = [];
+	var ilst = this.opt.trigraph.split(":");
+	if (!this.opt.trigraph || this.opt.trigraph[0] !== "A") {
+		throw "Bad trigraph definition: "+this.opt.trigraph;
+	}
+	for (var i = 0, ilen = ilst.length; i < ilen; i += 1) {
+		var str = ilst[i];
+		var config = {authors:[], year:0};
+		for (var j = 0, jlen = str.length; j < jlen; j += 1) {
+			switch (str[j]) {
+			case "A":
+				config.authors.push(1);
+				break;
+			case "a":
+				config.authors[config.authors.length - 1] += 1;
+				break;
+			case "0":
+				config.year += 1;
+				break;
+			default:
+				throw "Invalid character in trigraph definition: "+this.opt.trigraph;
+			}
+		}
+		params.push(config);
+	}
+	return params;
+};
 CSL.Engine.prototype.makeBibliography = function (bibsection) {
 	var debug, ret, params, maxoffset, item, len, pos, tok, tokk, tokkk, entry_ids, entry_strings, bibliography_errors;
 	debug = false;
@@ -2979,7 +3080,11 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre,
 	}
 	var ret = [];
 	if (flag === CSL.PREVIEW) {
-		ret = this.process_CitationCluster.call(this, citation.sortedItems, citation.citationID);
+		try {
+			ret = this.process_CitationCluster.call(this, citation.sortedItems, citation.citationID);
+		} catch (e) {
+			CSL.error("Error running CSL processor for preview: "+e);
+		}
 		this.registry.citationreg.citationByIndex = oldCitationList;
 		this.registry.citationreg.citationById = {};
 		for (i = 0, ilen = oldCitationList.length; i < ilen; i += 1) {
@@ -3317,10 +3422,7 @@ CSL.citeStart = function (Item, item) {
 	this.tmp.splice_delimiter = this[this.tmp.area].opt.layout_delimiter;
 	this.bibliography_sort.keys = [];
 	this.citation_sort.keys = [];
-	this.tmp.count_offset_characters = false;
-	this.tmp.offset_characters = 0;
 	this.tmp.has_done_year_suffix = false;
-	CSL.Util.Names.initNameSlices(this);
 	this.tmp.last_cite_locale = false;
 	if (!this.tmp.just_looking && item && !item.position && this.registry.registry[Item.id]) {
 		this.tmp.disambig_restore = CSL.cloneAmbigConfig(this.registry.registry[Item.id].disambig);
@@ -3337,9 +3439,6 @@ CSL.citeEnd = function (Item, item) {
 	this.tmp.last_names_used = this.tmp.names_used.slice();
 	this.tmp.cut_var = false;
 	this.tmp.disambig_request = false;
-	if (!this.tmp.suppress_decorations && this.tmp.offset_characters) {
-		this.registry.registry[Item.id].offset = this.tmp.offset_characters;
-	}
 	this.tmp.cite_locales.push(this.tmp.last_cite_locale);
 };
 CSL.Node = {};
@@ -3463,6 +3562,9 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) {
 	for (pos = 0, len = this.sys.xml.numberofnodes(nodes); pos < len; pos += 1) {
 		term = nodes[pos];
 		termname = this.sys.xml.getAttributeValue(term, 'name');
+		if (termname === "sub verbo") {
+			termname = "sub-verbo";
+		}
 		if ("undefined" === typeof this.locale[lang_out].terms[termname]) {
 			this.locale[lang_out].terms[termname] = {};
 		}
@@ -4156,6 +4258,11 @@ CSL.Node.key = {
 						}
 						state.output.append(num, this);
 					};
+				} else if (variable === "citation-label") {
+					func = function (state, Item) {
+						var trigraph = state.getCitationLabel(Item);
+						state.output.append(trigraph, this);
+					};
 				} else if (CSL.DATE_VARIABLES.indexOf(variable) > -1) {
 					func = function (state, Item) {
 						var dp, elem, value, e, yr, prefix, i, ilen, num;
@@ -4246,10 +4353,6 @@ CSL.Node.key = {
 			}
 		}
 		var end_key = new CSL.Token("key", CSL.END);
-		func = function (state, Item) {
-			state.output.closeLevel("empty");
-		};
-		end_key.execs.push(func);
 		func = function (state, Item) {
 			var keystring = state.output.string(state, state.output.queue);
 			if ("" === keystring) {
@@ -4282,6 +4385,9 @@ CSL.Node.label = {
 				this.strings.form = "long";
 			}
 			var func = function (state, Item, item) {
+				if (item && item.label === "sub verbo") {
+					item.label = "sub-verbo";
+				}
 				var termtxt = CSL.evaluateLabel(this, state, Item, item);
 				state.output.append(termtxt, this);
 			};
@@ -4442,9 +4548,6 @@ CSL.Node.macro = {
 };
 CSL.NameOutput = function(state, Item, item, variables) {
 	this.debug = false;
-	if (this.debug) {
-		print("(1)");
-	}
 	this.state = state;
 	this.Item = Item;
 	this.item = item;
@@ -4492,62 +4595,36 @@ CSL.NameOutput.prototype.outputNames = function () {
 	var i, ilen;
 	var variables = this.variables;
 	this.variable_offset = {};
-	if (this.debug) {
-		print("(2)");
+	if (this.family) {
+		this.family_decor = CSL.Util.cloneToken(this.family);
+		this.family_decor.strings.prefix = "";
+		this.family_decor.strings.suffix = "";
+	}
+	if (this.given) {
+		this.given_decor = CSL.Util.cloneToken(this.given);
+		this.given_decor.strings.prefix = "";
+		this.given_decor.strings.suffix = "";
 	}
 	this.getEtAlConfig();
-	if (this.debug) {
-		print("(3)");
-	}
 	this.divideAndTransliterateNames();
-	if (this.debug) {
-		print("(4)");
-	}
 	this.truncatePersonalNameLists();
-	if (this.debug) {
-		print("(5)");
-	}
 	this.constrainNames();
-	if (this.debug) {
-		print("(6)");
-	}
 	if (this.name.strings.form === "count") {
 		this.state.output.append(this.names_count, "empty");
 		return;
 	}
-	if (this.debug) {
-		print("(7)");
-	}
 	this.disambigNames();
-	if (this.debug) {
-		print("(8)");
-	}
 	this.setEtAlParameters();
-	if (this.debug) {
-		print("(9)");
-	}
 	this.setCommonTerm();
-	if (this.debug) {
-		print("(10)");
-	}
 	this.renderAllNames();
-	if (this.debug) {
-		print("(11)");
-	}
 	var blob_list = [];
 	for (i = 0, ilen = variables.length; i < ilen; i += 1) {
 		var v = variables[i];
 		var institution_sets = [];
 		var institutions = false;
-		if (this.debug) {
-			print("(11a)");
-		}
 		for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
 			institution_sets.push(this.joinPersonsAndInstitutions([this.persons[v][j], this.institutions[v][j]]));
 		}
-		if (this.debug) {
-			print("(11b)");
-		}
 		if (this.institutions[v].length) {
 			var pos = this.nameset_base + this.variable_offset[v];
 			if (this.freeters[v].length) {
@@ -4555,59 +4632,26 @@ CSL.NameOutput.prototype.outputNames = function () {
 			}
 			institutions = this.joinInstitutionSets(institution_sets, pos);
 		}
-		if (this.debug) {
-			print("(11c)");
-		}
 		var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]);
-		if (this.debug) {
-			print("(11d)");
-		}
 		if (varblob) {
 			varblob = this._applyLabels(varblob, v);
 			blob_list.push(varblob);
 		}
-		if (this.debug) {
-			print("(11e)");
-		}
 		if (this.common_term) {
 			break;
 		}
 	}
-	if (this.debug) {
-		print("(12)");
-	}
 	this.state.output.openLevel("empty");
 	this.state.output.current.value().strings.delimiter = this.names.strings.delimiter;
-	if (this.debug) {
-		print("(13)");
-	}
 	for (i = 0, ilen = blob_list.length; i < ilen; i += 1) {
 		this.state.output.append(blob_list[i], "literal", true);
 	}
-	if (this.debug) {
-		print("(14)");
-	}
 	this.state.output.closeLevel("empty");
-	if (this.debug) {
-		print("(15)");
-	}
 	var blob = this.state.output.pop();
-	if (this.debug) {
-		print("(16)");
-	}
 	this.state.output.append(blob, this.names);
-	if (this.debug) {
-		print("(17)");
-	}
-	if (this.debug) {
-		print("(18)");
-	}
 	this.state.tmp.name_node = this.state.output.current.value();
 	this._collapseAuthor();
 	this.variables = [];
-	if (this.debug) {
-		print("(19)");
-	}
 };
 CSL.NameOutput.prototype._applyLabels = function (blob, v) {
 	var txt;
@@ -4662,30 +4706,35 @@ CSL.NameOutput.prototype._buildLabel = function (term, plural, position) {
 	return ret;
 };
 CSL.NameOutput.prototype._collapseAuthor = function () {
-	var myqueue, mystr;
+	var myqueue, mystr, oldchars;
 	if ((this.item && this.item["suppress-author"] && this._author_is_first)
 		|| (this.state[this.state.tmp.area].opt.collapse 
 			&& this.state[this.state.tmp.area].opt.collapse.length)) {
 		if (this.state.tmp.authorstring_request) {
 			mystr = "";
 			myqueue = this.state.tmp.name_node.blobs.slice(-1)[0].blobs;
+			oldchars = this.state.tmp.offset_characters;
 			if (myqueue) {
 				mystr = this.state.output.string(this.state, myqueue, false);
 			}
+			this.state.tmp.offset_characters = oldchars;
 			this.state.registry.authorstrings[this.Item.id] = mystr;
 		} else if (!this.state.tmp.just_looking
 			&& !this.state.tmp.suppress_decorations) {
 			mystr = "";
 			myqueue = this.state.tmp.name_node.blobs.slice(-1)[0].blobs;
+			oldchars = this.state.tmp.offset_characters;
 			if (myqueue) {
 				mystr = this.state.output.string(this.state, myqueue, false);
 			}
 			if (mystr === this.state.tmp.last_primary_names_string) {
 				this.state.tmp.name_node.blobs.pop();
+				this.state.tmp.offset_characters = oldchars;
 			} else {
 				this.state.tmp.last_primary_names_string = mystr;
 				if (this.item && this.item["suppress-author"]) {
 					this.state.tmp.name_node.blobs.pop();
+					this.state.tmp.offset_characters = oldchars;
 				}
 				this.state.tmp.have_collapsed = false;
 			}
@@ -4834,9 +4883,8 @@ CSL.NameOutput.prototype._normalizeVariableValue = function (Item, variable) {
 		names = Item[variable].slice();
 	}
 	for (i = 0, ilen = names.length; i < ilen; i += 1) {
-		this._parseName(names[i]);
-		name = this.state.transform.name(this.state, names[i], this.state.opt["locale-pri"]);
-		names[i] = name;
+		names[i] = this.state.transform.name(this.state, names[i], this.state.opt["locale-pri"]);
+		names[i] = this._normalizeNameInput(names[i]);
 	}
 	return names;
 };
@@ -5351,12 +5399,12 @@ CSL.NameOutput.prototype._renderPersonalNames = function (values, pos) {
 	return ret;
 };
 CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i) {
-	var name = this._normalizeNameInput(value);
-	var dropping_particle = this._droppingParticle(name);
+	var name = value;
+	var dropping_particle = this._droppingParticle(name, pos);
 	var family = this._familyName(name);
 	var non_dropping_particle = this._nonDroppingParticle(name);
 	var given = this._givenName(name, pos, i);
-	var suffix = this._nameSuffix(name, pos);
+	var suffix = this._nameSuffix(name);
 	if (this._isShort(pos, i)) {
 		dropping_particle = false;
 		given = false;
@@ -5388,25 +5436,50 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i) {
 	} else if (this.name.strings["name-as-sort-order"] === "all" || (this.name.strings["name-as-sort-order"] === "first" && i === 0)) {
 		if (["always", "display-and-sort"].indexOf(this.state.opt["demote-non-dropping-particle"]) > -1) {
 			second = this._join([given, dropping_particle, non_dropping_particle], " ");
+			if (this.given) {
+				second.strings.prefix = this.given.strings.prefix;
+				second.strings.suffix = this.given.strings.suffix;
+			}
+			if (family && this.family) {
+				family.strings.prefix = this.family.strings.prefix;
+				family.strings.suffix = this.family.strings.suffix;
+			}
 			merged = this._join([family, second], sort_sep);
 			blob = this._join([merged, suffix], sort_sep);
 		} else {
 			first = this._join([non_dropping_particle, family], " ");
+			if (this.family) {
+				first.strings.prefix = this.family.strings.prefix;
+				first.strings.suffix = this.family.strings.suffix;
+			}
 			second = this._join([given, dropping_particle], " ");
+			if (this.given) {
+				second.strings.prefix = this.given.strings.prefix;
+				second.strings.suffix = this.given.strings.suffix;
+			}
 			merged = this._join([first, second], sort_sep);
 			blob = this._join([merged, suffix], sort_sep);
 		}
 	} else { // plain vanilla
 		if (name["dropping-particle"] && name.family && !name["non-dropping-particle"]) {
 			if (["'","\u02bc","\u2019"].indexOf(name["dropping-particle"].slice(-1)) > -1) {
-				name.family = name["dropping-particle"] + name.family;
-				name["dropping-particle"] = undefined;
+				family = this._join([dropping_particle, family], "");
 				dropping_particle = false;
-				family = this._familyName(name);
 			}
 		}
 		second = this._join([dropping_particle, non_dropping_particle, family], " ");
-		merged = this._join([given, second], " ");
+		if (this.family) {
+			second.strings.prefix = this.family.strings.prefix;
+			second.strings.suffix = this.family.strings.suffix;
+		}
+		if (this.given) {
+			given.strings.prefix = this.given.strings.prefix;
+			given.strings.suffix = this.given.strings.suffix;
+		}
+		if (second.strings.prefix) {
+			name["comma-dropping-particle"] = "";
+		}
+		merged = this._join([given, second], (name["comma-dropping-particle"] + " "));
 		blob = this._join([merged, suffix], suffix_sep);
 	}
 	return blob;
@@ -5420,6 +5493,7 @@ CSL.NameOutput.prototype._isShort = function (pos, i) {
 };
 CSL.NameOutput.prototype._normalizeNameInput = function (value) {
 	var name = {
+		literal:value.literal,
 		family:value.family,
 		given:value.given,
 		suffix:value.suffix,
@@ -5428,25 +5502,33 @@ CSL.NameOutput.prototype._normalizeNameInput = function (value) {
 		"dropping-particle":value["dropping-particle"],
 		"static-ordering":value["static-ordering"],
 		"parse-names":value["parse-names"],
+		"comma-dropping-particle": "",
 		block_initialize:value.block_initialize
 	};
 	this._parseName(name);
 	return name;
 };
 CSL.NameOutput.prototype._nonDroppingParticle = function (name) {
-	if (this.state.output.append(name["non-dropping-particle"], this.family, true)) {
+	if (this.state.output.append(name["non-dropping-particle"], this.family_decor, true)) {
 		return this.state.output.pop();
 	}
 	return false;
 };
-CSL.NameOutput.prototype._droppingParticle = function (name) {
-	if (this.state.output.append(name["dropping-particle"], this.family, true)) {
+CSL.NameOutput.prototype._droppingParticle = function (name, pos) {
+	if (name["dropping-particle"] && name["dropping-particle"].match(/^et.?al[^a-z]$/)) {
+		if (this.name.strings["et-al-use-last"]) {
+			this.etal_spec[pos] = 2;
+		} else {
+			this.etal_spec[pos] = 1;
+		}
+		name["comma-dropping-particle"] = "";
+	} else if (this.state.output.append(name["dropping-particle"], this.given_decor, true)) {
 		return this.state.output.pop();
 	}
 	return false;
 };
 CSL.NameOutput.prototype._familyName = function (name) {
-	if (this.state.output.append(name.family, this.family, true)) {
+	if (this.state.output.append(name.family, this.family_decor, true)) {
 		return this.state.output.pop();
 	}
 	return false;
@@ -5458,19 +5540,13 @@ CSL.NameOutput.prototype._givenName = function (name, pos, i) {
 	} else {
 		name.given = CSL.Util.Names.unInitialize(this.state, name.given);
 	}
-	if (this.state.output.append(name.given, this.given, true)) {
+	if (this.state.output.append(name.given, this.given_decor, true)) {
 		return this.state.output.pop();
 	}
 	return false;
 };
-CSL.NameOutput.prototype._nameSuffix = function (name, pos) {
-	if (name.suffix && name.suffix.match(/^et.?al[^a-z]$/)) {
-		if (this.name.strings["et-al-use-last"]) {
-			this.etal_spec[pos] = 2;
-		} else {
-			this.etal_spec[pos] = 1;
-		}
-	} else if (this.state.output.append(name.suffix, "empty", true)) {
+CSL.NameOutput.prototype._nameSuffix = function (name) {
+	if (this.state.output.append(name.suffix, "empty", true)) {
 		return this.state.output.pop();
 	}
 	return false;
@@ -5522,13 +5598,14 @@ CSL.NameOutput.prototype._parseName = function (name) {
 	if (name.family 
 		&& (name.family.slice(0, 1) === '"' && name.family.slice(-1) === '"')
 		|| (!name["parse-names"] && "undefined" !== typeof name["parse-names"])) {
+		name.family = name.family.slice(1, -1);
 		noparse = true;
 		name["parse-names"] = 0;
 	} else {
 		noparse = false;
 	}
 	if (!name["non-dropping-particle"] && name.family && !noparse) {
-		m = name.family.match(/^([ \'\u2019a-z]+\s+)/);
+		m = name.family.match(/^((?:[a-z][ \'\u2019a-z]*[\s+|\'\u2019]|[DVL][^ ]\s+|[DVL][^ ][^ ]\s+))/);
 		if (m) {
 			name.family = name.family.slice(m[1].length);
 			name["non-dropping-particle"] = m[1].replace(/\s+$/, "");
@@ -5538,18 +5615,25 @@ CSL.NameOutput.prototype._parseName = function (name) {
 		m = name.given.match(/(\s*,!*\s*)/);
 		if (m) {
 			idx = name.given.indexOf(m[1]);
-			if (name.given.slice(idx, idx + m[1].length).replace(/\s*/g, "").length === 2) {
-				name["comma-suffix"] = true;
+			var possible_suffix = name.given.slice(idx + m[1].length);
+			var possible_comma = name.given.slice(idx, idx + m[1].length).replace(/\s*/g, "");
+			if (possible_suffix.length <= 3) {
+				if (possible_comma.length === 2) {
+					name["comma-suffix"] = true;
+				}
+				name.suffix = possible_suffix;
+			} else if (!name["dropping-particle"] && name.given) {
+				name["dropping-particle"] = possible_suffix;
+				name["comma-dropping-particle"] = ",";
 			}
-			name.suffix = name.given.slice(idx + m[1].length);
 			name.given = name.given.slice(0, idx);
 		}
 	}
 	if (!name["dropping-particle"] && name.given) {
-		m = name.given.match(/^(\s+[ \'\u2019a-z]*[a-z])$/);
+		m = name.given.match(/(\s+)([a-z][ \'\u2019a-z]*)$/);
 		if (m) {
-			name.given = name.given.slice(0, m[1].length * -1);
-			name["dropping-particle"] = m[2].replace(/^\s+/, "");
+			name.given = name.given.slice(0, (m[1].length + m[2].length) * -1);
+			name["dropping-particle"] = m[2];
 		}
 	}
 };
@@ -6109,35 +6193,7 @@ CSL.Node.text = {
 					func = function (state, Item) {
 						label = Item["citation-label"];
 						if (!label) {
-							myname = state.getTerm("reference", "short", 0);
-							len = CSL.CREATORS.length;
-							for (pos = 0; pos < len; pos += 1) {
-								n = CSL.CREATORS[pos];
-								if (Item[n]) {
-									names = Item[n];
-									if (names && names.length) {
-										name = names[0];
-									}
-									if (name && name.family) {
-										myname = name.family.replace(/\s+/g, "_");
-									} else if (name && name.literal) {
-										myname = name.literal;
-										m = myname.toLowerCase().match(/^(a|the|an\s+)/, "");
-										if (m) {
-											myname = myname.slice(m[1].length);
-										}
-									}
-									break;
-								}
-							}
-							year = "0000";
-							if (Item.issued) {
-								dp = Item.issued["date-parts"];
-								if (dp && dp[0] && dp[0][0]) {
-									year = "" + dp[0][0];
-								}
-							}
-							label = myname + year;
+							label = state.getCitationLabel(Item);
 						}
 						suffix = "";
 						if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false) {
@@ -6322,7 +6378,11 @@ CSL.Attributes["@macro"] = function (state, arg) {
 	this.postponed_macro = arg;
 };
 CSL.Attributes["@term"] = function (state, arg) {
-	this.strings.term = arg;
+	if (arg === "sub verbo") {
+		this.strings.term = "sub-verbo";
+	} else {
+		this.strings.term = arg;
+	}
 };
 CSL.Attributes["@xmlns"] = function (state, arg) {};
 CSL.Attributes["@lang"] = function (state, arg) {
@@ -6469,7 +6529,8 @@ CSL.Attributes["@variable"] = function (state, arg) {
 						for (key in myitem[variable]) {
 							if (myitem[variable].hasOwnProperty(key)) {
 								x = true;
-								break;
+							} else {
+								x = false;
 							}
 						}
 					}
@@ -6647,18 +6708,27 @@ CSL.Attributes["@plural"] = function (state, arg) {
 };
 CSL.Attributes["@locator"] = function (state, arg) {
 	var func;
+	var trylabels = arg.replace("sub verbo", "sub-verbo");
+	trylabels = trylabels.split(/\s+/);
 	if (["if",  "else-if"].indexOf(this.name) > -1) {
 		func = function (state, Item, item) {
+			var ret = [];
 			var label;
 			if ("undefined" === typeof item || !item.label) {
 				label = "page";
+			} else if (item.label === "sub verbo") {
+				label = "sub-verbo";
 			} else {
 				label = item.label;
 			}
-			if (arg === label) {
-				return true;
+			for (var i = 0, ilen = trylabels.length; i < ilen; i += 1) {
+				if (trylabels[i] === label) {
+					ret.push(true);
+				} else {
+					ret.push(false);
+				}
 			}
-			return false;
+			return ret;
 		};
 		this.tests.push(func);
 	}
@@ -7023,16 +7093,14 @@ CSL.Util.Match = function () {
 	};
 	this.all = function (token, state, Item, item) {
 		var ret = true;
-		len = this.tests.length;
-		for (pos = 0; pos < len; pos += 1) {
-			func = this.tests[pos];
+		for (var i = 0, ilen = this.tests.length; i < ilen; i += 1) {
+			func = this.tests[i];
 			reslist = func.call(token, state, Item, item);
 			if ("object" !== typeof reslist) {
 				reslist = [reslist];
 			}
-			llen = reslist.length;
-			for (pos = 0; pos < len; pos += 1) {
-				if (!reslist[ppos]) {
+			for (var j = 0, jlen = reslist.length; j < jlen; j += 1) {
+				if (!reslist[j]) {
 					ret = false;
 					break;
 				}
@@ -7290,17 +7358,16 @@ CSL.Transform = function (state) {
 			"static-ordering":static_ordering_val,
 			"parse-names":name["parse-names"],
 			"comma-suffix":name["comma-suffix"],
+			"comma-dropping-particle":name["comma-dropping-particle"],
 			transliterated:transliterated,
 			block_initialize:block_initialize,
-			literal:name.literal
+			literal:name.literal,
+			isInstitution:name.isInstitution
 		};
 		if (static_ordering_freshcheck &&
 			!getStaticOrder(name, true)) {
 			name["static-ordering"] = false;
 		}
-		if (name.family && name.family.length && name.family.slice(0, 1) === '"' && name.family.slice(-1) === '"') {
-			name.family = name.family.slice(1, -1);
-		}
 		if (!name.literal && (!name.given && name.family && name.isInstitution)) {
 			name.literal = name.family;
 		}
@@ -7939,17 +8006,6 @@ CSL.Util.Names.stripRight = function (str) {
 	}
 	return str.slice(0, end);
 };
-CSL.Util.Names.initNameSlices = function (state) {
-	var len, pos;
-	state.tmp.names_cut = {
-		counts: [],
-		variable: {}
-	};
-	len = CSL.NAME_VARIABLES.length;
-	for (pos = 0; pos < len; pos += 1) {
-		state.tmp.names_cut.counts[CSL.NAME_VARIABLES[pos]] = 0;
-	}
-};
 CSL.Util.Dates = {};
 CSL.Util.Dates.year = {};
 CSL.Util.Dates.year["long"] = function (state, num) {
@@ -8103,9 +8159,8 @@ CSL.Util.substituteStart = function (state, target) {
 			bib_first.decorations = [["@display", "left-margin"]];
 			func = function (state, Item) {
 				if (!state.tmp.render_seen) {
+					bib_first.strings.first_blob = Item.id;
 					state.output.startTag("bib_first", bib_first);
-					state.tmp.count_offset_characters = true;
-					state.output.calculate_offset = true;
 				}
 			};
 			bib_first.execs.push(func);
@@ -8114,6 +8169,7 @@ CSL.Util.substituteStart = function (state, target) {
 			bib_first = new CSL.Token("group", CSL.START);
 			bib_first.decorations = [["@display", display]];
 			func = function (state, Item) {
+				bib_first.strings.first_blob = Item.id;
 				state.output.startTag("bib_first", bib_first);
 			};
 			bib_first.execs.push(func);
@@ -8144,19 +8200,14 @@ CSL.Util.substituteEnd = function (state, target) {
 		if (state.build.cls) {
 			func = function (state, Item) {
 				state.output.endTag("bib_first");
-				state.tmp.count_offset_characters = false;
-				state.output.calculate_offset = false;
 			};
 			this.execs.push(func);
 			state.build.cls = false;
-		}
-		if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) {
+		} else if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) {
 			bib_first_end = new CSL.Token("group", CSL.END);
 			func = function (state, Item) {
 				if (!state.tmp.render_seen) {
 					state.output.endTag(); // closes bib_first
-					state.tmp.count_offset_characters = false;
-					state.output.calculate_offset = false;
 				}
 			};
 			bib_first_end.execs.push(func);
@@ -9355,6 +9406,9 @@ CSL.Registry.prototype.compareRegistryTokens = function (a, b) {
 	return 0;
 };
 CSL.Registry.prototype.registerAmbigToken = function (akey, id, ambig_config, tainters) {
+	if (!this.registry[id]) {
+		CSL.debug("Warning: unregistered item: itemID=("+id+"), akey=("+akey+")");
+	}
 	if (this.registry[id] && this.registry[id].disambig && this.registry[id].disambig.names) {
 		for (var i = 0, ilen = ambig_config.names.length; i < ilen; i += 1) {
 			var new_names_params = ambig_config.names[i];