diff --git a/chrome/content/zotero/xpcom/citeproc.js b/chrome/content/zotero/xpcom/citeproc.js
index 1cf1cc775a..508366d50d 100644
--- a/chrome/content/zotero/xpcom/citeproc.js
+++ b/chrome/content/zotero/xpcom/citeproc.js
@@ -22,9 +22,62 @@ Copyright (c) 2009-2019 Frank Bennett
this program. If not, see
A configured instance of the process is built in two stages, + * using {@link CSL.Core.Build} and {@link CSL.Core.Configure}. + * The former sets up hash-accessible locale data and imports the CSL format file + * to be applied to the citations, + * transforming it into a one-dimensional token list, and + * registering functions and parameters on each token as appropriate. + * The latter sets jump-point information + * on tokens that constitute potential branch + * points, in a single back-to-front scan of the token list. + * This + * yields a token list that can be executed front-to-back by + * body methods available on the + * {@link CSL.Engine} class.
+ * + *This top-level {@link CSL} object itself carries + * constants that are needed during processing.
+ * @namespace A CSL citation formatter. + */ + +// IE6 does not implement Array.indexOf(). +// IE7 neither, according to rumour. + + +// Potential skip words: +// under; along; out; between; among; outside; inside; amid; amidst; against; toward; towards. +// See https://forums.zotero.org/discussion/30484/?Focus=159613#Comment_159613 + +'use strict'; + + var CSL = { - PROCESSOR_VERSION: "1.2.2", + + PROCESSOR_VERSION: "1.2.13", + + error: function(str) { // default error function + if ("undefined" === typeof Error) { + throw new Error("citeproc-js error: " + str); + } else { + throw "citeproc-js error: " + str; + } + }, + debug: function(str) { // default debug function + if ("undefined" === typeof console) { + dump("CSL: " + str + "\n"); + } else { + console.log("citeproc-js warning: " + str); + } + }, + LOCATOR_LABELS_REGEXP: new RegExp("^((art|ch|subch|col|fig|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\\.)\\s+(.*)"), + STATUTE_SUBDIV_PLAIN_REGEX: /(?:(?:^| )(?:art|bk|ch|subch|col|fig|fol|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\. *)/, STATUTE_SUBDIV_PLAIN_REGEX_FRONT: /(?:^\s*[.,;]*\s*(?:art|bk|ch|subch|col|fig|fol|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\. *)/, STATUTE_SUBDIV_STRINGS: { @@ -81,6 +134,7 @@ var CSL = { "verse": "vrs.", "volume": "vol." }, + LOCATOR_LABELS_MAP: { "art": "article", "bk": "book", @@ -127,12 +181,16 @@ var CSL = { "gazette": true, "report": true, "regulation": true, - "standard": true + "standard": true, + "patent": true }, checkNestedBrace: function(state) { if (state.opt.xclass === "note") { this.depth = 0; this.update = function(str) { + + // Receives affix string, returns with flipped parens. + var str = str ? str : ""; var lst = str.split(/([\(\)])/); for (var i=1,ilen=lst.length;iCalled on the state object.
+ */ CSL.expandMacro = function (macro_key_token, target) { var mkey, macro_nodes, end_of_macro, func; + mkey = macro_key_token.postponed_macro; + var sort_direction = macro_key_token.strings.sort_direction; + + // Decorations and affixes are in wrapper applied in cs:text macro_key_token = new CSL.Token("group", CSL.START); + var hasDate = false; var macroid = false; macro_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, 'macro', mkey); @@ -2042,20 +2629,28 @@ CSL.expandMacro = function (macro_key_token, target) { }; macro_key_token.execs.push(func); } + if (this.build.macro_stack.indexOf(mkey) > -1) { - throw "CSL processor error: call to macro \"" + mkey + "\" would cause an infinite loop"; + CSL.error("CSL processor error: call to macro \"" + mkey + "\" would cause an infinite loop"); } else { this.build.macro_stack.push(mkey); } + macro_key_token.cslid = macroid; + if (CSL.MODULE_MACROS[mkey]) { macro_key_token.juris = mkey; this.opt.update_mode = CSL.POSITION; } + // Macro group is treated as a real node in the style CSL.Node.group.build.call(macro_key_token, this, target, true); + + // Node does not exist in the CSL if (!this.cslXml.getNodeValue(macro_nodes)) { - throw "CSL style error: undefined macro \"" + mkey + "\""; + CSL.error("CSL style error: undefined macro \"" + mkey + "\""); } + + // Let's macro var mytarget = CSL.getMacroTarget.call(this, mkey); if (mytarget) { CSL.buildMacro.call(this, mytarget, macro_nodes); @@ -2074,8 +2669,11 @@ CSL.expandMacro = function (macro_key_token, target) { text_node.execs.push(func); target.push(text_node); } + + // Decorations and affixes are in wrapper applied in cs:text end_of_macro = new CSL.Token("group", CSL.END); end_of_macro.strings.sort_direction = sort_direction; + if (hasDate) { func = function (state) { if (state.tmp.extension) { @@ -2087,9 +2685,12 @@ CSL.expandMacro = function (macro_key_token, target) { if (macro_key_token.juris) { end_of_macro.juris = mkey; } + // Macro group is treated as a real node in the style CSL.Node.group.build.call(end_of_macro, this, target, true); + this.build.macro_stack.pop(); }; + CSL.getMacroTarget = function (mkey) { var mytarget = false; if (this.build.extension) { @@ -2100,6 +2701,7 @@ CSL.getMacroTarget = function (mkey) { } return mytarget; }; + CSL.buildMacro = function (mytarget, macro_nodes) { var builder = CSL.makeBuilder(this, mytarget); var mynode; @@ -2110,14 +2712,33 @@ CSL.buildMacro = function (mytarget, macro_nodes) { } builder(mynode); }; + CSL.configureMacro = function (mytarget) { if (!this.build.extension) { this.configureTokenList(mytarget); } }; + + +/** + * Convert XML node to token. + *This is called on an XML node. After extracting the name and attribute
+ * information from the node, it performs three operations. Attribute information
+ * relating to output formatting is stored on the node as an array of tuples,
+ * which fixes the sequence of execution of output functions to be invoked
+ * in the next phase of processing. Other attribute information is reduced
+ * to functions, and is pushed into an array on the token in no particular
+ * order, for later execution. The element name is used as a key to
+ * invoke the relevant build method of the target element.
+ * Element methods are defined in {@link CSL.Node}.
CSL.START,
+ * CSL.END or CSL.SINGLETON.
+ */
CSL.XmlToToken = function (state, tokentype, explicitTarget, var_stack) {
var name, txt, attrfuncs, attributes, decorations, token, key, target;
name = state.cslXml.nodename(this);
+ //CSL.debug(tokentype + " : " + name);
if (state.build.skip && state.build.skip !== name) {
return;
}
@@ -2129,13 +2750,16 @@ CSL.XmlToToken = function (state, tokentype, explicitTarget, var_stack) {
return;
}
if (!CSL.Node[state.cslXml.nodename(this)]) {
- throw "Undefined node name \"" + name + "\".";
+ CSL.error("Undefined node name \"" + name + "\".");
}
attrfuncs = [];
attributes = state.cslXml.attributes(this);
decorations = CSL.setDecorations.call(this, state, attributes);
token = new CSL.Token(name, tokentype);
if (tokentype !== CSL.END || name === "if" || name === "else-if" || name === "layout") {
+ //
+ // xml: more xml stuff
+ //
for (var key in attributes) {
if (attributes.hasOwnProperty(key)) {
if (tokentype === CSL.END && key !== "@language" && key !== "@locale") {
@@ -2146,8 +2770,7 @@ CSL.XmlToToken = function (state, tokentype, explicitTarget, var_stack) {
try {
CSL.Attributes[key].call(token, state, "" + attributes[key]);
} catch (e) {
- CSL.error(e);
- throw "CSL processor error, " + key + " attribute: " + e;
+ CSL.error(key + " attribute: " + e);
}
} else {
CSL.debug("warning: undefined attribute \""+key+"\" in style");
@@ -2165,26 +2788,47 @@ CSL.XmlToToken = function (state, tokentype, explicitTarget, var_stack) {
token.variables = var_stack.pop();
}
}
+ //
+ // !!!!!: eliminate diversion of tokens to separate
+ // token list (formerly used for reading in macros
+ // and terms).
+ //
if (explicitTarget) {
target = explicitTarget;
} else {
target = state[state.build.area].tokens;
}
+ // True flags real nodes in the style
CSL.Node[name].build.call(token, state, target, true);
};
+
+
+
+/*global CSL: true */
+
+
CSL.DateParser = function () {
+
+ /*
+ * Fixed values
+ */
+
+ // jse imperial years
var epochPairs = [
["\u660E\u6CBB", 1867],
["\u5927\u6B63", 1911],
["\u662D\u548C", 1925],
["\u5E73\u6210", 1988]
];
+
+ // years by jse imperial epoch
var epochYearByName = {};
for (var i=0,ilen=epochPairs.length; iThis is used for + * options like (now defunct) "font-family", where the option value + * cannot be determined until the attribute is processed. + * Need for this function might be reviewed at some point ...
+ * @param {String} template A template containing + *%%STRING%% and %%PARAM%%
+ * placeholders. See {@link CSL.Output.Formats.html} for
+ * examples.
+ */
CSL.substituteTwo = function (template) {
return function (param) {
var template2 = template.replace("%%PARAM%%", param);
@@ -3304,17 +4295,25 @@ CSL.substituteTwo = function (template) {
if (!list) {
return "";
} else {
+ //("string" === typeof list){
return template2.replace("%%STRING%%", list);
}
};
};
};
+
+/**
+ * Generate string functions for designated output mode.
+ * Only "html" (the default) is supported at present.
+ * @param {String} mode Either "html" or "rtf", eventually. + */ CSL.Mode = function (mode) { var decorations, params, param, func, val, args; decorations = {}; params = CSL.Output.Formats[mode]; for (param in params) { if (true) { + if ("@" !== param.slice(0, 1)) { decorations[param] = params[param]; continue; @@ -3322,6 +4321,7 @@ CSL.Mode = function (mode) { func = false; val = params[param]; args = param.split('/'); + if (typeof val === "string" && val.indexOf("%%STRING%%") > -1) { if (val.indexOf("%%PARAM%%") > -1) { func = CSL.substituteTwo(val); @@ -3333,8 +4333,9 @@ CSL.Mode = function (mode) { } else if (typeof val === "function") { func = val; } else { - throw "CSL.Compiler: Bad " + mode + " config entry for " + param + ": " + val; + CSL.error("Bad " + mode + " config entry for " + param + ": " + val); } + if (args.length === 1) { decorations[args[0]] = func; } else if (args.length === 2) { @@ -3347,8 +4348,24 @@ CSL.Mode = function (mode) { } return decorations; }; + + +/** + * Generate a separate list of formatting attributes. + *This generates a list of tuples containing attribute + * information relevant to output formatting, in the order + * fixed in the constant {@link CSL.FORMAT_KEY_SEQUENCE}. + * This function is called during {@link CSL.Core.Build}. + * Formatting hints are distilled to functions + * later, in the second compilation pass ({@link CSL.Core.Configure}).
+ * @param {Object} state The state object returned by + * {@link CSL.Engine}. + * @param {Object} attributes The hash object containing + * the attributes and values extracted from an XML node. + */ CSL.setDecorations = function (state, attributes) { var ret, key, pos; + // This applies a fixed processing sequence ret = []; for (pos in CSL.FORMAT_KEY_SEQUENCE) { if (true) { @@ -3361,10 +4378,12 @@ CSL.setDecorations = function (state, attributes) { } return ret; }; + CSL.Doppeler = function(rexStr, stringMangler) { var matchRex = new RegExp("(" + rexStr + ")", "g"); var splitRex = new RegExp(rexStr, "g"); this.split = function (str) { + // Normalize markup if (stringMangler) { str = stringMangler(str); } @@ -3382,6 +4401,7 @@ CSL.Doppeler = function(rexStr, stringMangler) { } var tag = match[i]; if (tag === "\'" && split[i+1].length > 0) { + // Fixes https://forums.zotero.org/discussion/comment/294317 split[i+1] = match[i] + split[i+1]; match[i] = ""; } @@ -3402,7 +4422,9 @@ CSL.Doppeler = function(rexStr, stringMangler) { return lst.join(""); }; }; + CSL.Engine.prototype.normalDecorIsOrphan = function (blob, params) { + //print("params: "+JSON.stringify(params)); if (params[1] === "normal") { var use_param = false; var all_the_decor; @@ -3412,8 +4434,11 @@ CSL.Engine.prototype.normalDecorIsOrphan = function (blob, params) { all_the_decor = blob.alldecor; } for (var k = all_the_decor.length - 1; k > -1; k += -1) { + //print(" all decor: "+JSON.stringify(all_the_decor[k])); for (var n = all_the_decor[k].length - 1; n > -1; n += -1) { + //print(" superior param"+n+": "+all_the_decor[k][n][0]); if (all_the_decor[k][n][0] === params[0]) { + //print(" HIT!"); if (all_the_decor[k][n][1] !== "normal") { use_param = true; } @@ -3426,6 +4451,10 @@ CSL.Engine.prototype.normalDecorIsOrphan = function (blob, params) { } return false; }; + +/*global CSL: true */ + + CSL.Engine.prototype.getCitationLabel = function (Item) { var label = ""; var params = this.getTrigraphParams(); @@ -3454,6 +4483,7 @@ CSL.Engine.prototype.getCitationLabel = function (Item) { if (name && name.family) { myname = name.family; myname = myname.replace(/^([ \'\u2019a-z]+\s+)/, ""); + } else if (name && name.literal) { myname = name.literal; } @@ -3477,6 +4507,7 @@ CSL.Engine.prototype.getCitationLabel = function (Item) { } } if (!label) { + // Try for something using title if (Item.title) { var skipWords = this.locale[this.opt.lang].opts["skip-words"]; var lst = Item.title.split(/\s+/); @@ -3505,11 +4536,12 @@ CSL.Engine.prototype.getCitationLabel = function (Item) { 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.slice(0,1) !== "A") { - throw "Bad trigraph definition: "+this.opt.trigraph; + CSL.error("Bad trigraph definition: "+this.opt.trigraph); } for (var i = 0, ilen = ilst.length; i < ilen; i += 1) { var str = ilst[i]; @@ -3526,13 +4558,16 @@ CSL.Engine.prototype.getTrigraphParams = function () { config.year += 1; break; default: - throw "Invalid character in trigraph definition: "+this.opt.trigraph; + CSL.error("Invalid character in trigraph definition: "+this.opt.trigraph); } } params.push(config); } return params; }; + +/*global CSL: true */ + CSL.Engine.prototype.setOutputFormat = function (mode) { this.opt.mode = mode; this.fun.decorate = CSL.Mode(mode); @@ -3541,6 +4576,7 @@ CSL.Engine.prototype.setOutputFormat = function (mode) { this.output[mode].tmp = {}; } }; + CSL.Engine.prototype.getSortFunc = function () { return function (a,b) { a = a.split("-"); @@ -3562,6 +4598,7 @@ CSL.Engine.prototype.getSortFunc = function () { } }; }; + CSL.Engine.prototype.setLangTagsForCslSort = function (tags) { var i, ilen; if (tags) { @@ -3572,6 +4609,7 @@ CSL.Engine.prototype.setLangTagsForCslSort = function (tags) { } this.opt['locale-sort'].sort(this.getSortFunc()); }; + CSL.Engine.prototype.setLangTagsForCslTransliteration = function (tags) { var i, ilen; this.opt['locale-translit'] = []; @@ -3582,6 +4620,7 @@ CSL.Engine.prototype.setLangTagsForCslTransliteration = function (tags) { } this.opt['locale-translit'].sort(this.getSortFunc()); }; + CSL.Engine.prototype.setLangTagsForCslTranslation = function (tags) { var i, ilen; this.opt['locale-translat'] = []; @@ -3592,6 +4631,7 @@ CSL.Engine.prototype.setLangTagsForCslTranslation = function (tags) { } this.opt['locale-translat'].sort(this.getSortFunc()); }; + CSL.Engine.prototype.setLangPrefsForCites = function (obj, conv) { var opt = this.opt['cite-lang-prefs']; if (!conv) { @@ -3600,12 +4640,17 @@ CSL.Engine.prototype.setLangPrefsForCites = function (obj, conv) { }; } var segments = ['Persons', 'Institutions', 'Titles', 'Journals', 'Publishers', 'Places']; + // Set values in place for (var i = 0, ilen = segments.length; i < ilen; i += 1) { var clientSegment = conv(segments[i]); var citeprocSegment = segments[i].toLowerCase(); if (!obj[clientSegment]) { continue; } + // + // Normalize the sequence of secondary and tertiary + // in the provided obj segment list. + // var supplements = []; while (obj[clientSegment].length > 1) { supplements.push(obj[clientSegment].pop()); @@ -3617,6 +4662,9 @@ CSL.Engine.prototype.setLangPrefsForCites = function (obj, conv) { while (supplements.length) { obj[clientSegment].push(supplements.pop()); } + // + // normalization done. + // var lst = opt[citeprocSegment]; while (lst.length) { lst.pop(); @@ -3626,6 +4674,7 @@ CSL.Engine.prototype.setLangPrefsForCites = function (obj, conv) { } } }; + CSL.Engine.prototype.setLangPrefsForCiteAffixes = function (affixList) { if (affixList && affixList.length === 48) { var affixes = this.opt.citeAffixes; @@ -3639,6 +4688,7 @@ CSL.Engine.prototype.setLangPrefsForCiteAffixes = function (affixList) { if ((count % 8) === 4) { if (!affixes[settings[i]]["locale-"+forms[j]].prefix && !affixes[settings[i]]["locale-"+forms[j]].suffix) { + value = affixList[count] ? affixList[count] : ""; affixes[settings[i]]["locale-" + forms[j]].prefix = value; value = affixList[count] ? affixList[count + 1] : ""; @@ -3656,6 +4706,7 @@ CSL.Engine.prototype.setLangPrefsForCiteAffixes = function (affixList) { this.opt.citeAffixes = affixes; } }; + CSL.Engine.prototype.setAutoVietnameseNamesOption = function (arg) { if (arg) { this.opt["auto-vietnamese-names"] = true; @@ -3663,15 +4714,24 @@ CSL.Engine.prototype.setAutoVietnameseNamesOption = function (arg) { this.opt["auto-vietnamese-names"] = false; } }; + CSL.Engine.prototype.setAbbreviations = function (arg) { if (this.sys.setAbbreviations) { this.sys.setAbbreviations(arg); } }; + CSL.Engine.prototype.setSuppressTrailingPunctuation = function (arg) { this.citation.opt.suppressTrailingPunctuation = !!arg; }; + +/*global CSL: true */ + CSL.Output = {}; +/** + * Output queue object. + * @class + */ CSL.Output.Queue = function (state) { this.levelname = ["top"]; this.state = state; @@ -3682,7 +4742,15 @@ CSL.Output.Queue = function (state) { this.formats = new CSL.Stack(tokenstore); this.current = new CSL.Stack(this.queue); }; + +// XXX This works, but causes a mismatch in api_cite +// Could insert a placeholder +// Better to have a function that spits out an independent blob. +// Is that possible though? +// Okay. Use queue.append() with fake_queue instead. CSL.Output.Queue.prototype.pop = function () { + // For some reason, state.output.current.value() here can be an array, + // not a blob ... ? var drip = this.current.value(); if (drip.length) { return drip.pop(); @@ -3690,10 +4758,12 @@ CSL.Output.Queue.prototype.pop = function () { return drip.blobs.pop(); } }; + CSL.Output.Queue.prototype.getToken = function (name) { var ret = this.formats.value()[name]; return ret; }; + CSL.Output.Queue.prototype.mergeTokenStrings = function (base, modifier) { var base_token, modifier_token, ret, key; base_token = this.formats.value()[base]; @@ -3720,6 +4790,8 @@ CSL.Output.Queue.prototype.mergeTokenStrings = function (base, modifier) { } return ret; }; + +// Store a new output format token based on another CSL.Output.Queue.prototype.addToken = function (name, modifier, token) { var newtok, attr; newtok = new CSL.Token("output"); @@ -3733,12 +4805,17 @@ CSL.Output.Queue.prototype.addToken = function (name, modifier, token) { } } newtok.decorations = token.decorations; + } if ("string" === typeof modifier) { newtok.strings.delimiter = modifier; } this.formats.value()[name] = newtok; }; + +// +// newFormat adds a new bundle of formatting tokens to +// the queue's internal stack of such bundles CSL.Output.Queue.prototype.pushFormats = function (tokenstore) { if (!tokenstore) { tokenstore = {}; @@ -3746,9 +4823,12 @@ CSL.Output.Queue.prototype.pushFormats = function (tokenstore) { tokenstore.empty = this.empty; this.formats.push(tokenstore); }; + + CSL.Output.Queue.prototype.popFormats = function () { this.formats.pop(); }; + CSL.Output.Queue.prototype.startTag = function (name, token) { var tokenstore = {}; if (this.state.tmp["doing-macro-with-date"] && this.state.tmp.extension) { @@ -3759,20 +4839,29 @@ CSL.Output.Queue.prototype.startTag = function (name, token) { this.pushFormats(tokenstore); this.openLevel(name); }; + CSL.Output.Queue.prototype.endTag = function (name) { this.closeLevel(name); this.popFormats(); }; + +// +// newlevel adds a new blob object to the end of the current +// list, and adjusts the current pointer so that subsequent +// appends are made to blob list of the new object. + CSL.Output.Queue.prototype.openLevel = function (token) { var blob, curr; if ("object" === typeof token) { + // delimiter, prefix, suffix, decorations from token blob = new CSL.Blob(undefined, token); } else if ("undefined" === typeof token) { blob = new CSL.Blob(undefined, this.formats.value().empty, "empty"); } else { if (!this.formats.value() || !this.formats.value()[token]) { - throw "CSL processor error: call to nonexistent format token \"" + token + "\""; + CSL.error("CSL processor error: call to nonexistent format token \"" + token + "\""); } + // delimiter, prefix, suffix, decorations from token blob = new CSL.Blob(undefined, this.formats.value()[token], token); } curr = this.current.value(); @@ -3782,7 +4871,17 @@ CSL.Output.Queue.prototype.openLevel = function (token) { curr.push(blob); this.current.push(blob); }; + +/** + * "merge" used to be real complicated, now it's real simple. + */ CSL.Output.Queue.prototype.closeLevel = function (name) { + // CLEANUP: Okay, so this.current.value() holds the blob at the + // end of the current list. This is wrong. It should + // be the parent, so that we have the choice of reading + // the affixes and decorations, or appending to its + // content. The code that manipulates blobs will be + // much simpler that way. if (name && name !== this.current.value().levelname) { CSL.error("Level mismatch error: wanted " + name + " but found " + this.current.value().levelname); } @@ -3791,12 +4890,21 @@ CSL.Output.Queue.prototype.closeLevel = function (name) { blob.strings.suffix = this.checkNestedBrace.update(blob.strings.suffix); } }; + +// +// append does the same thing as newlevel, except +// that the blob it pushes has text content, +// and the current pointer is not moved after the push. + CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePredecessor, noStripPeriods) { var token, blob, curr; var useblob = true; if (notSerious) { ignorePredecessor = true; } + // XXXXX Nasty workaround, but still an improvement + // over the reverse calls to the cs:date node build + // function that we had before. if (this.state.tmp["doing-macro-with-date"] && !notSerious) { if (tokname !== "macro-with-date") { return false; @@ -3814,6 +4922,7 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr if (!notSerious && this.state.tmp.element_trace && this.state.tmp.element_trace.value() === "suppress-me") { + return false; } blob = false; @@ -3828,18 +4937,30 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr token = tokname; } if (!token) { - throw "CSL processor error: unknown format token name: " + tokname; + CSL.error("CSL processor error: unknown format token name: " + tokname); } + // Unset delimiters must be left undefined until they reach the queue + // in order to discriminate unset from explicitly empty delimiters + // when inheriting a default value from a superior node. [??? really ???] if (token.strings && "undefined" === typeof token.strings.delimiter) { token.strings.delimiter = ""; } if ("string" === typeof str && str.length) { + + // Source (;?!»«): http://en.wikipedia.org/wiki/Space_(punctuation)#Breaking_and_non-breaking_spaces + // Source (:): http://forums.zotero.org/discussion/4933/localized-quotes/#Comment_88384 str = str.replace(/ ([:;?!\u00bb])/g, "\u202f$1").replace(/\u00ab /g, "\u00ab\u202f"); + this.last_char_rendered = str.slice(-1); + // This, and not the str argument below on flipflop, is the + // source of the flipflopper string source. str = str.replace(/\s+'/g, " \'"); if (!notSerious) { + // this condition for sort_LeadingApostropheOnNameParticle str = str.replace(/^'/g, " \'"); } + + // signal whether we end with terminal punctuation? if (!ignorePredecessor) { this.state.tmp.term_predecessor = true; this.state.tmp.in_cite_predecessor = true; @@ -3850,6 +4971,8 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr blob = new CSL.Blob(str, token); curr = this.current.value(); if ("undefined" === typeof curr && this.current.mystack.length === 0) { + // XXXX An operation like this is missing somewhere, this should NOT be necessary. + // Addresses error triggered in multi-layouts. this.current.mystack.push([]); curr = this.current.value(); } @@ -3861,9 +4984,10 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr this.state.tmp.term_predecessor_name = true; } } - if (!notSerious) { - this.state.parallel.AppendBlobPointer(curr); - } + // + // Caution: The parallel detection machinery will blow up if tracking + // variables are not properly initialized elsewhere. + // if ("string" === typeof str) { if ("string" === typeof blob.blobs) { if (blob.blobs.slice(0, 1) !== " ") { @@ -3880,6 +5004,14 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr } } if (blob.strings["text-case"]) { + // + // This one is _particularly_ hard to follow. It's not obvious, + // but the blob already contains the input string at this + // point, as blob.blobs -- it's a terminal node, as it were. + // The str variable also contains the input string, but + // that copy is not used for onward processing. We have to + // apply our changes to the blob copy. + // blob.blobs = CSL.Output.Formatters[blob.strings["text-case"]](this.state, str); } if (this.state.tmp.strip_periods && !noStripPeriods) { @@ -3895,6 +5027,11 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr } } } + // + // XXX: Beware superfluous code in your code. str in this + // case is not the source of the final rendered string. + // See note above. + // curr.push(blob); this.state.fun.flipflopper.processTags(blob); } else if (useblob) { @@ -3904,31 +5041,45 @@ CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePr } return true; }; + CSL.Output.Queue.prototype.string = function (state, myblobs, blob) { var i, ilen, j, jlen, b; + //if (blob && blob.strings.delimiter) { + // print("DELIMITER: "+blob.strings.delimiter+" on "+[x.blobs[0].num for each (x in myblobs)]); + //} + //var blobs, ret, blob_delimiter, i, params, blobjr, last_str, last_char, b, use_suffix, qres, addtoret, span_split, j, res, blobs_start, blobs_end, key, pos, len, ppos, llen, ttype, ltype, terminal, leading, delimiters, use_prefix, txt_esc; var txt_esc = CSL.getSafeEscape(this.state); var blobs = myblobs.slice(); var ret = []; + if (blobs.length === 0) { return ret; } + var blob_delimiter = ""; if (blob) { blob_delimiter = blob.strings.delimiter; } else { + //print("=== Setting false to start ==="); state.tmp.count_offset_characters = false; state.tmp.offset_characters = 0; } + if (blob && blob.new_locale) { blob.old_locale = state.opt.lang; state.opt.lang = blob.new_locale; } + var blobjr, use_suffix, use_prefix, params; for (var i = 0, ilen = blobs.length; i < ilen; i += 1) { blobjr = blobs[i]; + if (blobjr.strings.first_blob) { + // Being the Item.id of the the entry being rendered. + //print(" -- turning on counting"); state.tmp.count_offset_characters = blobjr.strings.first_blob; } + if ("string" === typeof blobjr.blobs) { if ("number" === typeof blobjr.num) { ret.push(blobjr); @@ -3937,8 +5088,11 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) { blobjr.blobs = blobjr.particle + blobjr.blobs; blobjr.particle = ""; } + // (skips empty strings) + //b = txt_esc(blobjr.blobs); b = txt_esc(blobjr.blobs); var blen = b.length; + if (!state.tmp.suppress_decorations) { for (j = 0, jlen = blobjr.decorations.length; j < jlen; j += 1) { params = blobjr.decorations[j]; @@ -3951,11 +5105,17 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) { b = state.fun.decorate[params[0]][params[1]].call(blobjr, state, b, params[2]); } } + // + // because we will rip out portions of the output + // queue before rendering, group wrappers need + // to produce no output if they are found to be + // empty. if (b && b.length) { b = txt_esc(blobjr.strings.prefix) + b + txt_esc(blobjr.strings.suffix); if (state.opt.development_extensions.csl_reverse_lookup_support && !state.tmp.suppress_decorations) { for (j = 0, jlen = blobjr.decorations.length; j < jlen; j += 1) { params = blobjr.decorations[j]; + if (params[0] === "@showid") { b = state.fun.decorate[params[0]][params[1]].call(blobjr, state, b, params[2]); } @@ -3970,6 +5130,7 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) { } else if (blobjr.blobs.length) { var addtoret = state.output.string(state, blobjr.blobs, blobjr); if (blob) { + // Patch up world-class weird bug in the ill-constructed code of mine. if ("string" !== addtoret && addtoret.length > 1 && blobjr.strings.delimiter) { var numberSeen = false; for (var j=0,jlen=addtoret.length;jThis is used internally by the Registry.
+ * + * [object] CSL Item + * [object] disambiguation parameters + * [boolean] If true, include first-reference-note-number value in cite + */ CSL.getAmbiguousCite = function (Item, disambig, visualForm, item) { var ret; var flags = this.tmp.group_context.tip; @@ -5607,7 +7458,10 @@ CSL.getAmbiguousCite = function (Item, disambig, visualForm, item) { variable_success: flags.variable_success, output_tip: flags.output_tip, label_form: flags.label_form, - parallel_conditions: flags.parallel_conditions, + parallel_condition: flags.parallel_condition, + parallel_result: flags.parallel_result, + no_repeat_condition: flags.no_repeat_condition, + parallel_repeats: flags.parallel_result, condition: flags.condition, force_suppress: flags.force_suppress, done_vars: flags.done_vars.slice() @@ -5621,10 +7475,12 @@ CSL.getAmbiguousCite = function (Item, disambig, visualForm, item) { position: 1, "near-note": true }; + if (item) { itemSupp.locator = item.locator; itemSupp.label = item.label; } + if (this.registry.registry[Item.id] && this.registry.citationreg.citationsByItemId && this.registry.citationreg.citationsByItemId[Item.id] @@ -5636,11 +7492,13 @@ CSL.getAmbiguousCite = function (Item, disambig, visualForm, item) { } this.tmp.area = "citation"; this.tmp.root = "citation"; - this.parallel.use_parallels = (this.parallel.use_parallels === true || this.parallel.use_parallels === null) ? null : false; var origSuppressDecorations = this.tmp.suppress_decorations; this.tmp.suppress_decorations = true; this.tmp.just_looking = true; + + // Do not reset shadow_numbers when running ambiguous cites CSL.getCite.call(this, Item, itemSupp, null, false); + // !!! for (var i=0,ilen=this.output.queue.length;iFor reference when the closing node of a macro + * or locale definition is encountered.
+ * @name CSL.Attributes.@name + * @function + */ CSL.Attributes["@name"] = function (state, arg) { this.strings.name = arg; }; + +/** + * Store the form attribute (of a term node) on the state object. + *For reference when the closing node of a macro + * or locale definition is encountered.
+ * @name CSL.Attributes.@form + * @function + */ CSL.Attributes["@form"] = function (state, arg) { this.strings.form = arg; }; + CSL.Attributes["@date-parts"] = function (state, arg) { this.strings["date-parts"] = arg; }; + CSL.Attributes["@range-delimiter"] = function (state, arg) { this.strings["range-delimiter"] = arg; }; + +/** + * Store macro tokens in a buffer on the state object. + *For reference when the enclosing text token is + * processed.
+ * @name CSL.Attributes.@macro + * @function + */ CSL.Attributes["@macro"] = function (state, arg) { this.postponed_macro = arg; }; + +/* + * CSL.Attributes["@prefer-jurisdiction"] = function (state, arg) { + * this.prefer_jurisdiction = true; + * }; + */ + CSL.Attributes["@term"] = function (state, arg) { if (arg === "sub verbo") { this.strings.term = "sub-verbo"; @@ -12066,28 +16114,79 @@ CSL.Attributes["@term"] = function (state, arg) { this.strings.term = arg; } }; + + +/* + * Ignore xmlns attribute. + *This should always be
http://purl.org/net/xbiblio/csl + * anyway. At least for the present we will blindly assume + * that it is.
+ * @name CSL.Attributes.@xmlns + * @function + */ CSL.Attributes["@xmlns"] = function () {}; + + +/* + * Store language attribute to a buffer field. + *Will be placed in the appropriate location + * when the element is processed.
+ * @name CSL.Attributes.@lang + * @function + */ CSL.Attributes["@lang"] = function (state, arg) { if (arg) { state.build.lang = arg; } }; + + +// Used as a flag during dates processing CSL.Attributes["@lingo"] = function () {}; + +// Used as a flag during dates processing CSL.Attributes["@macro-has-date"] = function () { this["macro-has-date"] = true; }; + +/* + * Store suffix string on token. + * @name CSL.Attributes.@suffix + * @function + */ CSL.Attributes["@suffix"] = function (state, arg) { this.strings.suffix = arg; }; + + +/* + * Store prefix string on token. + * @name CSL.Attributes.@prefix + * @function + */ CSL.Attributes["@prefix"] = function (state, arg) { this.strings.prefix = arg; }; + + +/* + * Store delimiter string on token. + * @name CSL.Attributes.@delimiter + * @function + */ CSL.Attributes["@delimiter"] = function (state, arg) { this.strings.delimiter = arg; }; + + +/* + * Store match evaluator on token. + */ CSL.Attributes["@match"] = function (state, arg) { this.match = arg; }; + + CSL.Attributes["@names-min"] = function (state, arg) { var val = parseInt(arg, 10); if (state[state.build.area].opt.max_number_of_names < val) { @@ -12095,9 +16194,11 @@ CSL.Attributes["@names-min"] = function (state, arg) { } this.strings["et-al-min"] = val; }; + CSL.Attributes["@names-use-first"] = function (state, arg) { this.strings["et-al-use-first"] = parseInt(arg, 10); }; + CSL.Attributes["@names-use-last"] = function (state, arg) { if (arg === "true") { this.strings["et-al-use-last"] = true; @@ -12105,12 +16206,16 @@ CSL.Attributes["@names-use-last"] = function (state, arg) { this.strings["et-al-use-last"] = false; } }; + CSL.Attributes["@sort"] = function (state, arg) { if (arg === "descending") { this.strings.sort_direction = CSL.DESCENDING; } }; + CSL.Attributes["@plural"] = function (state, arg) { + // Accepted values of plural attribute differ on cs:text + // and cs:label nodes. if ("always" === arg || "true" === arg) { this.strings.plural = 1; } else if ("never" === arg || "false" === arg) { @@ -12119,49 +16224,68 @@ CSL.Attributes["@plural"] = function (state, arg) { this.strings.plural = false; } }; + CSL.Attributes["@has-publisher-and-publisher-place"] = function () { this.strings["has-publisher-and-publisher-place"] = true; }; + CSL.Attributes["@publisher-delimiter-precedes-last"] = function (state, arg) { this.strings["publisher-delimiter-precedes-last"] = arg; }; + CSL.Attributes["@publisher-delimiter"] = function (state, arg) { this.strings["publisher-delimiter"] = arg; }; + CSL.Attributes["@publisher-and"] = function (state, arg) { this.strings["publisher-and"] = arg; }; + CSL.Attributes["@newdate"] = function () {}; + + CSL.Attributes["@givenname-disambiguation-rule"] = function (state, arg) { if (CSL.GIVENNAME_DISAMBIGUATION_RULES.indexOf(arg) > -1) { state.citation.opt["givenname-disambiguation-rule"] = arg; } }; + CSL.Attributes["@collapse"] = function (state, arg) { + // only one collapse value will be honoured. if (arg) { state[this.name].opt.collapse = arg; } }; + CSL.Attributes["@cite-group-delimiter"] = function (state, arg) { if (arg) { state[state.tmp.area].opt.cite_group_delimiter = arg; } }; + + + CSL.Attributes["@names-delimiter"] = function (state, arg) { state.setOpt(this, "names-delimiter", arg); }; + CSL.Attributes["@name-form"] = function (state, arg) { state.setOpt(this, "name-form", arg); }; + CSL.Attributes["@subgroup-delimiter"] = function (state, arg) { this.strings["subgroup-delimiter"] = arg; }; + CSL.Attributes["@subgroup-delimiter-precedes-last"] = function (state, arg) { this.strings["subgroup-delimiter-precedes-last"] = arg; }; + + CSL.Attributes["@name-delimiter"] = function (state, arg) { state.setOpt(this, "name-delimiter", arg); }; + CSL.Attributes["@et-al-min"] = function (state, arg) { var val = parseInt(arg, 10); if (state[state.build.area].opt.max_number_of_names < val) { @@ -12169,9 +16293,11 @@ CSL.Attributes["@et-al-min"] = function (state, arg) { } state.setOpt(this, "et-al-min", val); }; + CSL.Attributes["@et-al-use-first"] = function (state, arg) { state.setOpt(this, "et-al-use-first", parseInt(arg, 10)); }; + CSL.Attributes["@et-al-use-last"] = function (state, arg) { if (arg === "true") { state.setOpt(this, "et-al-use-last", true); @@ -12179,6 +16305,7 @@ CSL.Attributes["@et-al-use-last"] = function (state, arg) { state.setOpt(this, "et-al-use-last", false); } }; + CSL.Attributes["@et-al-subsequent-min"] = function (state, arg) { var val = parseInt(arg, 10); if (state[state.build.area].opt.max_number_of_names < val) { @@ -12186,35 +16313,46 @@ CSL.Attributes["@et-al-subsequent-min"] = function (state, arg) { } state.setOpt(this, "et-al-subsequent-min", val); }; + CSL.Attributes["@et-al-subsequent-use-first"] = function (state, arg) { state.setOpt(this, "et-al-subsequent-use-first", parseInt(arg, 10)); }; + CSL.Attributes["@suppress-min"] = function (state, arg) { this.strings["suppress-min"] = parseInt(arg, 10); }; + CSL.Attributes["@suppress-max"] = function (state, arg) { this.strings["suppress-max"] = parseInt(arg, 10); }; + + CSL.Attributes["@and"] = function (state, arg) { state.setOpt(this, "and", arg); }; + CSL.Attributes["@delimiter-precedes-last"] = function (state, arg) { state.setOpt(this, "delimiter-precedes-last", arg); }; + CSL.Attributes["@delimiter-precedes-et-al"] = function (state, arg) { state.setOpt(this, "delimiter-precedes-et-al", arg); }; + CSL.Attributes["@initialize-with"] = function (state, arg) { state.setOpt(this, "initialize-with", arg); }; + CSL.Attributes["@initialize"] = function (state, arg) { if (arg === "false") { state.setOpt(this, "initialize", false); } }; + CSL.Attributes["@name-as-reverse-order"] = function (state, arg) { this["name-as-reverse-order"] = arg; }; + CSL.Attributes["@name-as-sort-order"] = function (state, arg) { if (this.name === "style-options") { this["name-as-sort-order"] = arg; @@ -12222,52 +16360,68 @@ CSL.Attributes["@name-as-sort-order"] = function (state, arg) { state.setOpt(this, "name-as-sort-order", arg); } }; + CSL.Attributes["@sort-separator"] = function (state, arg) { state.setOpt(this, "sort-separator", arg); }; + CSL.Attributes["@require-match"] = function (state, arg) { if (arg === "true") { this.requireMatch = true; } }; + CSL.Attributes["@exclude-types"] = function (state, arg) { state.bibliography.opt.exclude_types = arg.split(/\s+/); }; + CSL.Attributes["@exclude-with-fields"] = function (state, arg) { state.bibliography.opt.exclude_with_fields = arg.split(/\s+/); }; + + CSL.Attributes["@year-suffix-delimiter"] = function (state, arg) { state[this.name].opt["year-suffix-delimiter"] = arg; }; + CSL.Attributes["@after-collapse-delimiter"] = function (state, arg) { state[this.name].opt["after-collapse-delimiter"] = arg; }; + CSL.Attributes["@subsequent-author-substitute"] = function (state, arg) { state[this.name].opt["subsequent-author-substitute"] = arg; }; + CSL.Attributes["@subsequent-author-substitute-rule"] = function (state, arg) { state[this.name].opt["subsequent-author-substitute-rule"] = arg; }; + CSL.Attributes["@disambiguate-add-names"] = function (state, arg) { if (arg === "true") { state.opt["disambiguate-add-names"] = true; } }; + CSL.Attributes["@disambiguate-add-givenname"] = function (state, arg) { if (arg === "true") { state.opt["disambiguate-add-givenname"] = true; } }; + CSL.Attributes["@disambiguate-add-year-suffix"] = function (state, arg) { if (arg === "true" && state.opt.xclass !== "numeric") { state.opt["disambiguate-add-year-suffix"] = true; } }; + + CSL.Attributes["@second-field-align"] = function (state, arg) { if (arg === "flush" || arg === "margin") { state[this.name].opt["second-field-align"] = arg; } }; + + CSL.Attributes["@hanging-indent"] = function (state, arg) { if (arg === "true") { if (state.opt.development_extensions.hanging_indent_legacy_number) { @@ -12277,19 +16431,26 @@ CSL.Attributes["@hanging-indent"] = function (state, arg) { } } }; + + CSL.Attributes["@line-spacing"] = function (state, arg) { if (arg && arg.match(/^[.0-9]+$/)) { state[this.name].opt["line-spacing"] = parseFloat(arg, 10); } }; + + CSL.Attributes["@entry-spacing"] = function (state, arg) { if (arg && arg.match(/^[.0-9]+$/)) { state[this.name].opt["entry-spacing"] = parseFloat(arg, 10); } }; + + CSL.Attributes["@near-note-distance"] = function (state, arg) { state[this.name].opt["near-note-distance"] = parseInt(arg, 10); }; + CSL.Attributes["@text-case"] = function (state, arg) { var func = function (state, Item) { if (arg === "normal") { @@ -12305,15 +16466,25 @@ CSL.Attributes["@text-case"] = function (state, arg) { }; this.execs.push(func); }; + + CSL.Attributes["@page-range-format"] = function (state, arg) { state.opt["page-range-format"] = arg; }; + + CSL.Attributes["@year-range-format"] = function (state, arg) { state.opt["year-range-format"] = arg; }; + + CSL.Attributes["@default-locale"] = function (state, arg) { if (this.name === 'style') { var lst, len, pos, m, ret; + // + // Workaround for Internet Exploder 6 (doesn't recognize + // groups in str.split(/something(braced-group)something/) + // var m = arg.match(/-x-(sort|translit|translat)-/g); if (m) { for (pos = 0, len = m.length; pos < len; pos += 1) { @@ -12340,48 +16511,73 @@ CSL.Attributes["@default-locale"] = function (state, arg) { this.default_locale = true; } }; + CSL.Attributes["@default-locale-sort"] = function (state, arg) { state.opt["default-locale-sort"] = arg; }; + CSL.Attributes["@demote-non-dropping-particle"] = function (state, arg) { state.opt["demote-non-dropping-particle"] = arg; }; + CSL.Attributes["@initialize-with-hyphen"] = function (state, arg) { if (arg === "false") { state.opt["initialize-with-hyphen"] = false; } }; + CSL.Attributes["@institution-parts"] = function (state, arg) { this.strings["institution-parts"] = arg; }; + CSL.Attributes["@if-short"] = function (state, arg) { if (arg === "true") { this.strings["if-short"] = true; } }; + CSL.Attributes["@substitute-use-first"] = function (state, arg) { this.strings["substitute-use-first"] = parseInt(arg, 10); }; + CSL.Attributes["@use-first"] = function (state, arg) { this.strings["use-first"] = parseInt(arg, 10); }; + CSL.Attributes["@stop-last"] = function (state, arg) { this.strings["stop-last"] = parseInt(arg, 10) * -1; }; + CSL.Attributes["@use-last"] = function (state, arg) { this.strings["use-last"] = parseInt(arg, 10); }; + + CSL.Attributes["@reverse-order"] = function (state, arg) { if ("true" === arg) { this.strings["reverse-order"] = true; } }; + CSL.Attributes["@display"] = function (state, arg) { if (state.bibliography.tokens.length === 2) { state.opt.using_display = true; } this.strings.cls = arg; }; + + +/*global CSL: true */ + + +/** + * String stack object. + *Numerous string stacks are used to track nested + * parameters at runtime. This class provides methods + * that remove some of the aggravation of managing + * them.
+ * @class + */ CSL.Stack = function (val, literal) { this.mystack = []; if (literal || val) { @@ -12389,6 +16585,11 @@ CSL.Stack = function (val, literal) { } this.tip = this.mystack[0]; }; + +/** + * Push a value onto the stack. + *This just does what it says.
+ */ CSL.Stack.prototype.push = function (val, literal) { if (literal || val) { this.mystack.push(val); @@ -12397,13 +16598,26 @@ CSL.Stack.prototype.push = function (val, literal) { } this.tip = this.mystack[this.mystack.length - 1]; }; + +/** + * Clear the stack + */ CSL.Stack.prototype.clear = function () { this.mystack = []; this.tip = {}; }; + +/** + * Replace the top value on the stack. + *This removes some ugly syntax from the + * main code.
+ */ CSL.Stack.prototype.replace = function (val, literal) { + // + // safety fix after a bug was chased down. Rhino + // JS will process a negative index without error (!). if (this.mystack.length === 0) { - throw "Internal CSL processor error: attempt to replace nonexistent stack item with " + val; + CSL.error("Internal CSL processor error: attempt to replace nonexistent stack item with " + val); } if (literal || val) { this.mystack[(this.mystack.length - 1)] = val; @@ -12412,6 +16626,12 @@ CSL.Stack.prototype.replace = function (val, literal) { } this.tip = this.mystack[this.mystack.length - 1]; }; + + +/** + * Remove the top value from the stack. + *Just does what it says.
+ */ CSL.Stack.prototype.pop = function () { var ret = this.mystack.pop(); if (this.mystack.length) { @@ -12421,480 +16641,182 @@ CSL.Stack.prototype.pop = function () { } return ret; }; + + +/** + * Return the top value on the stack. + *Removes a little hideous complication from + * the main code.
+ */ CSL.Stack.prototype.value = function () { return this.mystack.slice(-1)[0]; }; + + +/** + * Return length (depth) of stack. + *Used to identify if there is content to + * be handled on the stack
+ */ CSL.Stack.prototype.length = function () { return this.mystack.length; }; + +/*global CSL: true */ + +/** + * Initializes the parallel cite tracking arrays + */ CSL.Parallel = function (state) { this.state = state; - this.sets = new CSL.Stack([]); - this.try_cite = true; - this.use_parallels = false; - this.midVars = ["section", "volume", "container-title", "collection-number", "issue", "page-first", "page", "number"]; - this.ignoreVarsLawGeneral = ["first-reference-note-number", "locator", "label","page-first","page","genre"]; - this.ignoreVarsLawProceduralHistory = ["issued", "first-reference-note-number", "locator", "label","page-first","page","genre","jurisdiction"]; - this.ignoreVarsOrders = ["first-reference-note-number"]; - this.ignoreVarsOther = ["first-reference-note-number", "locator", "label","section","page-first","page"]; -}; -CSL.Parallel.prototype.isMid = function (variable) { - return (this.midVars.indexOf(variable) > -1); + this.info = {}; }; + +CSL.Parallel.prototype.setSeriesRels = function(prevID, currID, seriesRels) { + if (this.info[prevID][currID]) { + if (!seriesRels) { + seriesRels = JSON.parse(JSON.stringify(this.info[prevID])); + } + } else { + seriesRels = false; + } + return seriesRels; +} + +CSL.Parallel.prototype.getRepeats = function(prev, curr) { + var rex = /(?:type|id|seeAlso|.*-sub|.*-subjoin|.*-main)/; + var ret = {}; + for (var key in prev) { + if (key.match(rex)) { + continue; + } + if (typeof prev[key] === "string") { + if (prev[key] && prev[key] === curr[key]) { + ret[key] = true; + } + } else if (typeof prev[key] === "object") { + // Could do better than this. + if (JSON.stringify(prev[key]) === JSON.stringify(curr[key])) { + ret[key] = true; + } + } + } + return ret; +} + CSL.Parallel.prototype.StartCitation = function (sortedItems, out) { this.parallel_conditional_blobs_list = []; - if (this.use_parallels) { - this.sortedItems = sortedItems; - this.sortedItemsPos = -1; - this.sets.clear(); - this.sets.push([]); - this.in_series = true; - this.delim_counter = 0; - this.delim_pointers = []; - if (out) { - this.out = out; - } else { - this.out = this.state.output.queue; - } - this.master_was_neutral_cite = true; - } -}; -CSL.Parallel.prototype.StartCite = function (Item, item, prevItemID) { - var position, len, pos, curr, master, last_id, prev_locator, curr_locator; - if (this.use_parallels) { - if (this.sets.value().length && this.sets.value()[0].itemId == Item.id) { - this.ComposeSet(); - } - this.sortedItemsPos += 1; - if (item) { - position = item.position; - } - this.try_cite = true; - var has_required_var = false; - for (var i = 0, ilen = CSL.PARALLEL_MATCH_VARS.length; i < ilen; i += 1) { - if (Item[CSL.PARALLEL_MATCH_VARS[i]]) { - has_required_var = true; - break; - } - } - var basics_ok = true; - var last_cite = this.sets.value().slice(-1)[0]; - if (last_cite && last_cite.Item) { - var lastJuris = last_cite.Item.jurisdiction ? last_cite.Item.jurisdiction.split(":")[0] : ""; - var thisJuris = Item.jurisdiction ? Item.jurisdiction.split(":")[0] : ""; - if (last_cite.Item.title !== Item.title) { - basics_ok = false; - } else if (lastJuris !== thisJuris) { - basics_ok = false; - } else if (last_cite.Item.type !== Item.type) { - basics_ok = false; - } else if (["article-journal","article-magazine"].indexOf(Item.type) > -1) { - if (!this.state.opt.development_extensions.handle_parallel_articles - || last_cite.Item["container-title"] !== Item["container-title"]) { - basics_ok = false; - } - } - } - if (!basics_ok || !has_required_var || CSL.PARALLEL_TYPES.indexOf(Item.type) === -1) { - this.try_cite = true; - if (this.in_series) { - this.in_series = false; - } - } - this.cite = {}; - this.cite.front = []; - this.cite.mid = []; - this.cite.back = []; - this.cite.front_collapse = {}; - this.cite.back_forceme = []; - this.cite.position = position; - this.cite.Item = Item; - this.cite.itemId = "" + Item.id; - this.cite.prevItemID = "" + prevItemID; - this.target = "front"; - if (["treaty"].indexOf(Item.type) > -1) { - this.ignoreVars = this.ignoreVarsOrders; - } else if (["article-journal","article-magazine"].indexOf(Item.type) > -1) { - this.ignoreVars = this.ignoreVarsOther; - } else if (item && item.prefix) { - this.ignoreVars = this.ignoreVarsLawProceduralHistory; - this.cite.useProceduralHistory = true; - var prev = this.sets.value()[(this.sets.value().length - 1)]; - if (prev && prev.back) { - for (var i=prev.back.length-1;i>-1;i+=-1) { - if (prev.back[i] && prev[prev.back[i]]) { - delete prev[prev.back[i]]; + this.info = {}; + if (sortedItems.length > 1) { + // Harder than it looks. + // On a first pass, get the seeAlso of each item. + for (var i=0,ilen=sortedItems.length; iThis class provides the tokens that define + * the runtime version of the style. The tokens are + * instantiated by {@link CSL.Core.Build}, but the token list + * must be post-processed with + * {@link CSL.Core.Configure} before it can be used to generate + * citations.
+ * @param {String} name The node name represented by this token. + * @param {Int} tokentype A flag indicating whether this token + * marks the start of a node, the end of a node, or is a singleton. + * @class + */ CSL.Token = function (name, tokentype, conditional) { + /** + * Name of the element. + *This corresponds to the element name of the + * relevant tag in the CSL file. + */ this.name = name; + /** + * Strings and other static content specific to the element. + */ this.strings = {}; this.strings.delimiter = undefined; this.strings.prefix = ""; this.strings.suffix = ""; + /** + * Formatting parameters. + *
This is a placeholder at instantiation. It is + * replaced by the result of {@link CSL.setDecorations} + * when the tag is created and configured during {@link CSL.Core.Build} + * by {@link CSL.XmlToToken}. The parameters for particular + * formatting attributes are stored as string arrays, which + * map to formatting functions at runtime, + * when the output format is known. Note that the order in which + * parameters are registered is fixed by the constant + * {@link CSL.FORMAT_KEY_SEQUENCE}. + */ this.decorations = []; this.variables = []; + /** + * Element functions. + *
Functions implementing the styling behaviour of the element + * are pushed into this array in the {@link CSL.Core.Build} phase. + */ this.execs = []; + /** + * Token type. + *
This is a flag constant indicating whether the token represents + * a start tag, an end tag, or is a singleton.
+ */ this.tokentype = tokentype; + + // Conditional attributes added to bare tokens at runtime + + /** + * Condition evaluator. + *This is a placeholder that receives a single function, and is
+ * only relevant for a conditional branching tag (if or
+ * else-if). The function implements the argument to
+ * the match= attribute (any, all
+ * or none), by executing the functions registered in the
+ * tests array (see below), and reacting accordingly. This
+ * function is invoked by the execution wrappers found in
+ * {@link CSL.Engine}.
Functions that evaluate to true or false, implementing + * various posisble attributes to the conditional branching tags, + * are registered here during {@link CSL.Core.Build}. + *
+ */ + // this.tests = []; + /** + * Jump point on success. + *This holds the list jump point to be used when the
+ * evaluator function of a conditional tag
+ * returns true (success). The jump index value is set during the
+ * back-to-front token pass performed during {@link CSL.Core.Configure}.
+ *
This holds the list jump point to be used when the
+ * evaluator function of a conditional tag
+ * returns false (failure). Jump index values are set during the
+ * back-to-front token pass performed during {@link CSL.Core.Configure}.
+ *
This holds the index of the next token in the + * token list, which is the default "jump-point" for ordinary + * processing. Jump index values are set during the + * back-to-front token pass performed during {@link CSL.Core.Configure}. + *
+ */ + // this.next = false; }; + +// Have needed this for yonks CSL.Util.cloneToken = function (token) { var newtok, key, pos, len; if ("string" === typeof token) { @@ -13447,6 +17650,8 @@ CSL.Util.cloneToken = function (token) { if (token.variables) { newtok.variables = token.variables.slice(); } + // Probably overkill; this is only used for cloning formatting + // tokens. if (token.execs) { newtok.execs = token.execs.slice(); if (token.tests) { @@ -13455,6 +17660,13 @@ CSL.Util.cloneToken = function (token) { } return newtok; }; + +/*global CSL: true */ + +/** + * Ambiguous Cite Configuration Object + * @class + */ CSL.AmbigConfig = function () { this.maxvals = []; this.minval = 1; @@ -13463,9 +17675,13 @@ CSL.AmbigConfig = function () { this.year_suffix = false; this.disambiguate = 0; }; + +/*global CSL: true */ + CSL.Blob = function (str, token, levelname) { var len, pos, key; this.levelname = levelname; + //print(levelname); if (token) { this.strings = {"prefix":"","suffix":""}; for (var key in token.strings) { @@ -13498,15 +17714,34 @@ CSL.Blob = function (str, token, levelname) { } this.alldecor = [this.decorations]; }; + + CSL.Blob.prototype.push = function (blob) { if ("string" === typeof this.blobs) { - throw "Attempt to push blob onto string object"; + CSL.error("Attempt to push blob onto string object"); } else if (false !== blob) { blob.alldecor = blob.alldecor.concat(this.alldecor); this.blobs.push(blob); } }; + +/*global CSL: true */ + +/** + * An output instance object representing a number or a range + * + * with attributes next and start, and + * methods isRange(), renderStart(), renderEnd() and renderRange(). + * At render time, the output queue will perform optional + * collapsing of these objects in the queue, according to + * configurable options, and apply any decorations registered + * in the object to the output elements. + * @namespace Range object and friends. + */ + CSL.NumericBlob = function (particle, num, mother_token, id) { + // item id is used to assure that prefix delimiter is invoked only + // when joining blobs across items this.id = id; this.alldecor = []; this.num = num; @@ -13540,14 +17775,20 @@ CSL.NumericBlob = function (particle, num, mother_token, id) { this.formatter = new CSL.Output.DefaultFormatter(); } }; + + CSL.NumericBlob.prototype.setFormatter = function (formatter) { this.formatter = formatter; this.type = this.formatter.format(1); }; + + CSL.Output.DefaultFormatter = function () {}; + CSL.Output.DefaultFormatter.prototype.format = function (num) { return num.toString(); }; + CSL.NumericBlob.prototype.checkNext = function (next,start) { if (start) { this.status = CSL.START; @@ -13576,9 +17817,16 @@ CSL.NumericBlob.prototype.checkNext = function (next,start) { next.status = CSL.SUCCESSOR; } } + // wakes up the correct delimiter. + //if (this.status === CSL.SEEN) { + // this.status = CSL.SUCCESSOR; + //} } }; + + CSL.NumericBlob.prototype.checkLast = function (last) { + // Used to adjust final non-range join if (this.status === CSL.SEEN || (last.num !== (this.num - 1) && this.status === CSL.SUCCESSOR)) { this.status = CSL.SUCCESSOR; @@ -13586,11 +17834,20 @@ CSL.NumericBlob.prototype.checkLast = function (last) { } return false; }; + +/*global CSL: true */ + CSL.Util.fixDateNode = function (parent, pos, node) { var form, variable, datexml, subnode, partname, attr, val, prefix, suffix, children, subchildren, display, cslid; + var lingo = this.cslXml.getAttributeValue(node, "lingo"); + var default_locale = this.cslXml.getAttributeValue(node, "default-locale"); + + // Raise date flag, used to control inclusion of year-suffix key in sorts + // This may be a little reckless: not sure what happens on no-date conditions this.build.date_key = true; + form = this.cslXml.getAttributeValue(node, "form"); var lingo; if (default_locale) { @@ -13598,31 +17855,61 @@ CSL.Util.fixDateNode = function (parent, pos, node) { } else { lingo = this.cslXml.getAttributeValue(node, "lingo"); } + if (!this.getDate(form, default_locale)) { return parent; } + var dateparts = this.cslXml.getAttributeValue(node, "date-parts"); + variable = this.cslXml.getAttributeValue(node, "variable"); prefix = this.cslXml.getAttributeValue(node, "prefix"); suffix = this.cslXml.getAttributeValue(node, "suffix"); display = this.cslXml.getAttributeValue(node, "display"); cslid = this.cslXml.getAttributeValue(node, "cslid"); + + // + // Xml: Copy a node + // datexml = this.cslXml.nodeCopy(this.getDate(form, default_locale)); this.cslXml.setAttribute(datexml, 'lingo', this.opt.lang); this.cslXml.setAttribute(datexml, 'form', form); this.cslXml.setAttribute(datexml, 'date-parts', dateparts); this.cslXml.setAttribute(datexml, "cslid", cslid); + // + // Xml: Set attribute + // this.cslXml.setAttribute(datexml, 'variable', variable); this.cslXml.setAttribute(datexml, 'default-locale', default_locale); + // + // Xml: Set flag + // if (prefix) { + // + // Xml: Set attribute + // this.cslXml.setAttribute(datexml, "prefix", prefix); } if (suffix) { + // + // Xml: Set attribute + // this.cslXml.setAttribute(datexml, "suffix", suffix); } if (display) { + // + // Xml: Set attribute + // this.cslXml.setAttribute(datexml, "display", display); } + // + // Step through any date-part children of the layout date node, + // and lay their attributes onto the corresponding node in the + // locale template node copy. + // + // tests: language_BaseLocale + // tests: date_LocalizedTextInStyleLocaleWithTextCase + // children = this.cslXml.children(datexml); for (var key in children) { subnode = children[key]; @@ -13633,6 +17920,7 @@ CSL.Util.fixDateNode = function (parent, pos, node) { } } } + children = this.cslXml.children(node); for (var key in children) { subnode = children[key]; @@ -13653,26 +17941,55 @@ CSL.Util.fixDateNode = function (parent, pos, node) { } } } + if ("year" === this.cslXml.getAttributeValue(node, "date-parts")) { + + // + // Xml: Find one node by attribute and delete + // this.cslXml.deleteNodeByNameAttribute(datexml, 'month'); + // + // Xml: Find one node by attribute and delete + // this.cslXml.deleteNodeByNameAttribute(datexml, 'day'); + } else if ("year-month" === this.cslXml.getAttributeValue(node, "date-parts")) { + // + // Xml: Find one node by attribute and delete + // this.cslXml.deleteNodeByNameAttribute(datexml, 'day'); } else if ("month-day" === this.cslXml.getAttributeValue(node, "date-parts")) { + // + // Xml: Get child nodes + // var childNodes = this.cslXml.children(datexml); for (var i=1,ilen=this.cslXml.numberofnodes(childNodes);ishort, long
+ */ CSL.Util.Dates.year = {}; + +/** + * Convert year to long form + *This just passes the number back as a string.
+ */ CSL.Util.Dates.year["long"] = function (state, num) { if (!num) { if ("boolean" === typeof num) { @@ -13944,6 +18353,11 @@ CSL.Util.Dates.year["long"] = function (state, num) { } return num.toString(); }; + +/** + * Crudely convert to Japanese Imperial form. + *Returns the result as a string.
+ */ CSL.Util.Dates.year.imperial = function (state, num, end) { var year = ""; if (!num) { @@ -13983,6 +18397,8 @@ CSL.Util.Dates.year.imperial = function (state, num, end) { if (label && offset) { var normalizedKey = label; if (state.sys.normalizeAbbrevsKey) { + // The first argument does not need to specify the exact variable + // name. normalizedKey = state.sys.normalizeAbbrevsKey("number", label); } if (!state.transform.abbrevs['default']['number'][normalizedKey]) { @@ -13995,12 +18411,23 @@ CSL.Util.Dates.year.imperial = function (state, num, end) { } return year; }; + +/** + * Convert year to short form + *Just crops any 4-digit year to the last two digits.
+ */ CSL.Util.Dates.year["short"] = function (state, num) { num = num.toString(); if (num && num.length === 4) { return num.substr(2); } }; + + +/** + * Convert year to short form + *Just crops any 4-digit year to the last two digits.
+ */ CSL.Util.Dates.year.numeric = function (state, num) { var m, pre; num = "" + num; @@ -14017,6 +18444,13 @@ CSL.Util.Dates.year.numeric = function (state, num) { } return (pre + num); }; + + +/* + * MONTH manglers + * normalize + * long, short, numeric, numeric-leading-zeros + */ CSL.Util.Dates.normalizeMonth = function (num, useSeason) { var ret; if (!num) { @@ -14049,7 +18483,13 @@ CSL.Util.Dates.normalizeMonth = function (num, useSeason) { } return ret; }; + CSL.Util.Dates.month = {}; + +/** + * Convert month to numeric form + *This just passes the number back as a string.
+ */ CSL.Util.Dates.month.numeric = function (state, num) { var num = CSL.Util.Dates.normalizeMonth(num); if (!num) { @@ -14057,6 +18497,11 @@ CSL.Util.Dates.month.numeric = function (state, num) { } return num; }; + +/** + * Convert month to numeric-leading-zeros form + *This just passes the number back as string padded with zeros.
+ */ CSL.Util.Dates.month["numeric-leading-zeros"] = function (state, num) { var num = CSL.Util.Dates.normalizeMonth(num); if (!num) { @@ -14069,6 +18514,14 @@ CSL.Util.Dates.month["numeric-leading-zeros"] = function (state, num) { } return num; }; + +/** + * Convert month to long form + *This passes back the month of the locale in long form.
+ */ + +// Gender is not currently used. Is it needed? + CSL.Util.Dates.month["long"] = function (state, num, gender, forceDefaultLocale) { var res = CSL.Util.Dates.normalizeMonth(num, true); var num = res.num; @@ -14083,6 +18536,14 @@ CSL.Util.Dates.month["long"] = function (state, num, gender, forceDefaultLocale) } return num; }; + +/** + * Convert month to long form + *This passes back the month of the locale in short form.
+ */ + +// See above. + CSL.Util.Dates.month["short"] = function (state, num, gender, forceDefaultLocale) { var res = CSL.Util.Dates.normalizeMonth(num, true); var num = res.num; @@ -14097,11 +18558,27 @@ CSL.Util.Dates.month["short"] = function (state, num, gender, forceDefaultLocale } return num; }; + +/* + * DAY manglers + * numeric, numeric-leading-zeros, ordinal + */ CSL.Util.Dates.day = {}; + +/** + * Convert day to numeric form + *This just passes the number back as a string.
+ */ CSL.Util.Dates.day.numeric = function (state, num) { return num.toString(); }; + CSL.Util.Dates.day["long"] = CSL.Util.Dates.day.numeric; + +/** + * Convert day to numeric-leading-zeros form + *This just passes the number back as a string padded with zeros.
+ */ CSL.Util.Dates.day["numeric-leading-zeros"] = function (state, num) { if (!num) { num = 0; @@ -14112,10 +18589,29 @@ CSL.Util.Dates.day["numeric-leading-zeros"] = function (state, num) { } return num.toString(); }; + +/** + * Convert day to ordinal form + *This will one day pass back the number as a string with the + * ordinal suffix appropriate to the locale. For the present, + * it just does what is most of the time right for English.
+ */ CSL.Util.Dates.day.ordinal = function (state, num, gender) { return state.fun.ordinalizer.format(num, gender); }; + +/*global CSL: true */ + +/** + * Helper functions for constructing sort keys. + * @namespace Sort key utilities + */ CSL.Util.Sort = {}; + +/** + * Strip prepositions from a string + *Used when generating sort keys.
+ */ CSL.Util.Sort.strip_prepositions = function (str) { var m; if ("string" === typeof str) { @@ -14127,9 +18623,18 @@ CSL.Util.Sort.strip_prepositions = function (str) { } return str; }; + +/*global CSL: true */ + CSL.Util.substituteStart = function (state, target) { var element_trace, display, bib_first, func, choose_start, if_start, nodetypes; - func = function (state) { + func = function (state, Item, item) { + if (item && item.parallel) { + state.tmp.group_context.tip.parallel_result = item.parallel; + } + if (item && item.repeats && Object.keys(item.repeats).length > 0) { + state.tmp.group_context.tip.parallel_repeats = item.repeats; + } for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) { if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) { state.tmp.strip_periods += 1; @@ -14143,6 +18648,10 @@ CSL.Util.substituteStart = function (state, target) { this.decorations.push(["@showid","true", this.cslid]); this.decorations.reverse(); } + // + // Contains body code for both substitute and first-field/remaining-fields + // formatting. + // nodetypes = ["number", "date", "names"]; if (("text" === this.name && !this.postponed_macro) || nodetypes.indexOf(this.name) > -1) { element_trace = function (state, Item, item) { @@ -14165,8 +18674,16 @@ CSL.Util.substituteStart = function (state, target) { state.tmp.element_trace.push("suppress-me"); } } + /* + if (!state.tmp.just_looking && item && item["suppress-author"]) { + if (state.tmp.probably_rendered_something) { + //state.tmp.element_trace.push("suppress-me"); + } + } + */ } else { if (!state.tmp.just_looking && item && item["author-only"] && state.tmp.area !== "intext") { + // XXX can_block_substitute probably is doing nothing here. The value is always true. if (!state.tmp.probably_rendered_something && state.tmp.can_block_substitute) { } else { state.tmp.element_trace.push("suppress-me"); @@ -14181,6 +18698,13 @@ CSL.Util.substituteStart = function (state, target) { display = this.strings.cls; this.strings.cls = false; if (state.build.render_nesting_level === 0) { + // + // The markup formerly known as @bibliography/first + // + // Separate second-field-align from the generic display logic. + // There will be some code replication, but not in the + // assembled style. + // if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) { bib_first = new CSL.Token("group", CSL.START); bib_first.decorations = [["@display", "left-margin"]]; @@ -14205,10 +18729,26 @@ CSL.Util.substituteStart = function (state, target) { state.build.cls = display; } state.build.render_nesting_level += 1; + // Should this be render_nesting_level, with the increment + // below? ... ? if (state.build.substitute_level.value() === 1) { + // + // All top-level elements in a substitute environment get + // wrapped in conditionals. The substitute_level variable + // is a stack, because spanned names elements (with their + // own substitute environments) can be nested inside + // a substitute environment. + // + // (okay, we use conditionals a lot more than that. + // we slot them in for author-only as well...) choose_start = new CSL.Token("choose", CSL.START); CSL.Node.choose.build.call(choose_start, state, target); if_start = new CSL.Token("if", CSL.START); + // + // Set a test of the shadow if token to skip this + // macro if we have acquired a name value. + + // check for variable func = function () { if (state.tmp.can_substitute.value()) { return true; @@ -14220,11 +18760,15 @@ CSL.Util.substituteStart = function (state, target) { if_start.test = state.fun.match.any(this, state, if_start.tests); target.push(if_start); } + if (state.sys.variableWrapper && this.variables_real && this.variables_real.length) { + func = function (state, Item, item) { if (!state.tmp.just_looking && !state.tmp.suppress_decorations) { + // Attach item data and variable names. + // Do with them what you will. var variable_entry = new CSL.Token("text", CSL.START); variable_entry.decorations = [["@showid", "true"]]; state.output.startTag("variable_entry", variable_entry); @@ -14250,6 +18794,7 @@ CSL.Util.substituteStart = function (state, target) { firstReferenceNoteNumber = item['first-reference-note-number']; } var citationNumber = 0; + // XXX Will this EVER happen? if (item && item['citation-number']) { citationNumber = item['citation-number']; } @@ -14275,10 +18820,14 @@ CSL.Util.substituteStart = function (state, target) { this.execs.push(func); } }; + + CSL.Util.substituteEnd = function (state, target) { var func, bib_first_end, bib_other, if_end, choose_end, author_substitute, str; + if (state.sys.variableWrapper && (this.hasVariable || (this.variables_real && this.variables_real.length))) { + func = function (state) { if (!state.tmp.just_looking && !state.tmp.suppress_decorations) { state.output.endTag("variable_entry"); @@ -14286,6 +18835,7 @@ CSL.Util.substituteEnd = function (state, target) { }; this.execs.push(func); } + func = function (state) { for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) { if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) { @@ -14295,6 +18845,7 @@ CSL.Util.substituteEnd = function (state, target) { } }; this.execs.push(func); + state.build.render_nesting_level += -1; if (state.build.render_nesting_level === 0) { if (state.build.cls) { @@ -14305,6 +18856,7 @@ CSL.Util.substituteEnd = function (state, target) { state.build.cls = false; } else if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) { bib_first_end = new CSL.Token("group", CSL.END); + // first func end func = function (state) { if (!state.tmp.render_seen) { state.output.endTag("bib_first"); // closes bib_first @@ -14330,6 +18882,7 @@ CSL.Util.substituteEnd = function (state, target) { choose_end = new CSL.Token("choose", CSL.END); CSL.Node.choose.build.call(choose_end, state, target); } + if ("names" === this.name || ("text" === this.name && this.variables_real !== "title")) { author_substitute = new CSL.Token("text", CSL.SINGLETON); func = function (state, Item) { @@ -14345,19 +18898,25 @@ CSL.Util.substituteEnd = function (state, target) { if (state.tmp.substituted_variable !== this.variables_real) { return; } + var subrule = state.bibliography.opt["subsequent-author-substitute-rule"]; var i, ilen; + //var text_esc = CSL.getSafeEscape(state); var printing = !state.tmp.suppress_decorations; if (printing && state.tmp.subsequent_author_substitute_ok) { if (state.tmp.rendered_name) { if ("partial-each" === subrule || "partial-first" === subrule) { var dosub = true; var rendered_name = []; + // This is a wee bit risky, as we're assuming that the name + // children and the list of stringified names are congruent. + // That *should* always be true, but you never know. for (i = 0, ilen = state.tmp.name_node.children.length; i < ilen; i += 1) { var name = state.tmp.rendered_name[i]; if (dosub && state.tmp.last_rendered_name && state.tmp.last_rendered_name.length > (i - 1) && name && !name.localeCompare(state.tmp.last_rendered_name[i])) { + str = new CSL.Blob(state[state.tmp.area].opt["subsequent-author-substitute"]); state.tmp.name_node.children[i].blobs = [str]; if ("partial-first" === subrule) { @@ -14368,6 +18927,7 @@ CSL.Util.substituteEnd = function (state, target) { } rendered_name.push(name); } + // might want to slice this? state.tmp.last_rendered_name = rendered_name; } else if ("complete-each" === subrule) { var rendered_name = state.tmp.rendered_name.join(","); @@ -14403,8 +18963,17 @@ CSL.Util.substituteEnd = function (state, target) { }; this.execs.push(func); } + if (("text" === this.name && !this.postponed_macro) || ["number", "date", "names"].indexOf(this.name) > -1) { + // element trace func = function (state, Item) { + // element_trace is a mess, but it's trying to do something simple. + // A queue append is done, and element_trace.value() returns "suppress-me" + // the append is aborted. That's it. + // It seems only to be used on numeric elements of numeric styles ATM. + // If used only for that purpose, it could be greatly simplified. + // If cleaned up, it could do more interesting things, like control + // the suppression of names set later than first position. if (state.tmp.element_trace.mystack.length>1) { state.tmp.element_trace.pop(); } @@ -14412,6 +18981,9 @@ CSL.Util.substituteEnd = function (state, target) { this.execs.push(func); } }; + +/*global CSL: true */ + CSL.Util.padding = function (num) { var m = num.match(/\s*(-{0,1}[0-9]+)/); if (m) { @@ -14426,14 +18998,18 @@ CSL.Util.padding = function (num) { } return num; }; + CSL.Util.LongOrdinalizer = function () {}; + CSL.Util.LongOrdinalizer.prototype.init = function (state) { this.state = state; }; + CSL.Util.LongOrdinalizer.prototype.format = function (num, gender) { if (num < 10) { num = "0" + num; } + // Argument true means "loose". var ret = CSL.Engine.getField( CSL.LOOSE, this.state.locale[this.state.opt.lang].terms, @@ -14445,13 +19021,17 @@ CSL.Util.LongOrdinalizer.prototype.format = function (num, gender) { if (!ret) { ret = this.state.fun.ordinalizer.format(num, gender); } + // Probably too optimistic -- what if only renders in _sort? this.state.tmp.cite_renders_content = true; return ret; }; + + CSL.Util.Ordinalizer = function (state) { this.state = state; this.suffixes = {}; }; + CSL.Util.Ordinalizer.prototype.init = function () { if (!this.suffixes[this.state.opt.lang]) { this.suffixes[this.state.opt.lang] = {}; @@ -14469,6 +19049,7 @@ CSL.Util.Ordinalizer.prototype.init = function () { } } }; + CSL.Util.Ordinalizer.prototype.format = function (num, gender) { var str; num = parseInt(num, 10); @@ -14498,6 +19079,8 @@ CSL.Util.Ordinalizer.prototype.format = function (num, gender) { } } else { if (!gender) { + // XXX hack to prevent crash on CSL 1.0 styles. + // Reported by Carles. gender = undefined; } this.state.fun.ordinalizer.init(); @@ -14516,7 +19099,9 @@ CSL.Util.Ordinalizer.prototype.format = function (num, gender) { str = str += suffix; return str; }; + CSL.Util.Romanizer = function () {}; + CSL.Util.Romanizer.prototype.format = function (num) { var ret, pos, n, numstr, len; ret = ""; @@ -14533,13 +19118,29 @@ CSL.Util.Romanizer.prototype.format = function (num) { } return ret; }; + + +/** + * Create a suffix formed from a list of arbitrary characters of arbitrary length. + *This is a lot harder than it seems.
+ */ CSL.Util.Suffixator = function (slist) { if (!slist) { slist = CSL.SUFFIX_CHARS; } this.slist = slist.split(","); }; + +/** + * The format method. + *This method is used in generating ranges. Every numeric + * formatter (of which Suffixator is one) must be an instantiated + * object with such a "format" method.
+ */ + CSL.Util.Suffixator.prototype.format = function (N) { + // Many thanks to Avram Lyon for this code, and good + // riddance to the several functions that it replaces. var X; N += 1; var key = ""; @@ -14550,9 +19151,61 @@ CSL.Util.Suffixator.prototype.format = function (N) { } while ( N !== 0 ); return key; }; + + CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { + //print("** processNumber() ItemObject[variable]="+ItemObject[variable]); var val; + var me = this; + + var fullformAnd = ",\\s+and\\s+|\\s+and\\s+"; + if (this.opt.lang.slice(0, 2) !== "en") { + fullformAnd += "|,\\s+" + this.getTerm("and") + "\\s+|\\s+" + this.getTerm("and") + "\\s+"; + } + var symbolAnd = "\\s*&\\s*"; + var andRex = new RegExp("^" + symbolAnd+ "$"); + var joinerMatchRex = new RegExp("(" + symbolAnd + "|" + fullformAnd + "|;\\s+|,\\s+|\\s*\\\\*[\\-\\u2013]+\\s*)", "g"); + var joinerSplitRex = new RegExp("(?:" + symbolAnd + "|" + fullformAnd + "|;\\s+|,\\s+|\\s*\\\\*[\\-\\u2013]+\\s*|\\s*&\\s*)"); + + // This guesses whether the symbol form is defined or not. + // It's the best we can do, because when locales are built, all of the + // holes are filled explictly with fallback values: the symbol form is never undefined. + var localeAnd = this.getTerm("and"); + var localeAmpersand = this.getTerm("and", "symbol"); + if (localeAnd === localeAmpersand) { + localeAmpersand = "&"; + } + + // XXXX shadow_numbers should carry an array of objects with + // XXXX full data for each. The test of a number should be + // XXXX a separate function, possibly supported by a splitter + // XXXX method also used here. Keep code for each action in one place, + // XXXX to prevent debugging from becoming a nightmare. + + // The capture pattern below would apply affixes to all sub-elements, + // which is not what we want. Sub-elements should nest within, or + // affixes should be edited. The latter is probably easier to handle. + + // values = [ + // { + // label: "sec.", + // label-form: "plural", + // value: 100, + // styling: [object], + // numeric: true + // joiningSuffix: " & ", + // }, + // { + // label: "sec.", + // label-form: "none", + // value: 103, + // styling: [object], + // numeric: true, + // joiningSuffix: "" + // } + // ] + function normalizeFieldValue(str) { str = str.trim(); var m = str.match(/^([^ ]+)/); @@ -14573,12 +19226,16 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } return str; } + + function composeNumberInfo(origLabel, label, val, joiningSuffix) { joiningSuffix = joiningSuffix ? joiningSuffix : ""; var info = {}; + if (!label && !CSL.STATUTE_SUBDIV_STRINGS_REVERSE[variable]) { label = "var:"+variable; } + if (label) { var m = label.match(/(\s*)([^\s]+)(\s*)/); info.label = m[2]; @@ -14587,7 +19244,9 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { info.plural = 0; info.labelVisibility = false; } + var m = val.match(/^([0-9]*[a-zA-Z]+0*)?([0-9]+(?:[a-zA-Z]*|[-,a-zA-Z]+))$/); + //var m = val.match(/^([0-9]*[a-zA-Z]0*)([0-9]+(?:[a-zA-Z]*|[-,a-zA-Z]+))$/); if (m) { info.particle = m[1] ? m[1] : ""; info.value = m[2]; @@ -14598,7 +19257,10 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { info.joiningSuffix = joiningSuffix.replace(/\s*-\s*/, "-"); return info; } + function fixupSubsections(elems) { + // This catches things like p. 12a-c, recombining content to yield + // numeric true despite the hyphen. for (var i=elems.length-2;i>-1;i-=2) { if (elems[i] === "-" && elems[i-1].match(/^(?:(?:[a-z]|[a-z][a-z]|[a-z][a-z][a-z]|[a-z][a-z][a-z][a-z])\. *)*[0-9]+[,a-zA-Z]+$/) @@ -14609,16 +19271,26 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } return elems; } + function parseString(str, defaultLabel) { defaultLabel = defaultLabel ? defaultLabel : ""; + str = normalizeFieldValue(str, defaultLabel); + + // Split chunks and collate delimiters. var elems = []; - var m = str.match(/(;\s+|,\s+|\s*\\*[\-\u2013]+\s*|\s*&\s*)/g); + var m = str.match(joinerMatchRex); if (m) { - var lst = str.split(/(?:;\s+|,\s+|\s*\\*[\-\u2013]+\s*|\s*&\s*)/); + for (var i=0, ilen=m.length; iThe headline says it all. The source code for this + * object can be used as a template for producing other + * output modes.
+ */ CSL.Output.Formats.prototype.html = { + // + // text_escape: Format-specific function for escaping text destined + // for output. Takes the text to be escaped as sole argument. Function + // will be run only once across each portion of text to be escaped, it + // need not be idempotent. + // "text_escape": function (text) { + // Numeric entities, in case the output is processed as + // xml in an environment in which HTML named entities are + // not declared. if (!text) { text = ""; } @@ -16091,6 +21063,7 @@ CSL.Output.Formats.prototype.html = { .replace(/\s\s/g, "\u00A0 ") .replace(CSL.SUPERSCRIPTS_REGEXP, function(aChar) { + // return "<sup>" + CSL.SUPERSCRIPTS[aChar] + "</sup>"; return "" + CSL.SUPERSCRIPTS[aChar] + ""; }); }, @@ -16120,15 +21093,34 @@ CSL.Output.Formats.prototype.html = { }, "@quotes/inner": function (state, str) { if ("undefined" === typeof str) { + // + // Mostly right by being wrong (for apostrophes) + // return "\u2019"; } return state.getTerm("open-inner-quote") + str + state.getTerm("close-inner-quote"); }, "@quotes/false": false, + //"@bibliography/body": function (state,str){ + // return "This is a persistent store of disambiguation and
+ * sort order information relating to individual items
+ * for which rendering is requested. Item data is stored
+ * in a hash, with the item key as hash key, for quick
+ * retrieval. A virtual sequence within the hashed store
+ * is maintained on the fly as items are added to the
+ * store, using *_next and *_prev
+ * attributes on each item. A separate hash of items
+ * based on their undisambiguated cite form is
+ * maintained, and the item id list and disambiguation
+ * level for each set of disambiguation partners is shared
+ * through the registry item.
Nested, because keys are an array.
+ */ CSL.Registry.Comparifier = function (state, keyset) { var sort_directions, len, pos, compareKeys; var sortCompare = CSL.getSortCompare(state.opt["default-locale-sort"]); @@ -16951,6 +22351,15 @@ CSL.Registry.Comparifier = function (state, keyset) { this.compareKeys = function (a, b) { len = a.sortkeys ? a.sortkeys.length : 0; for (pos = 0; pos < len; pos += 1) { + // + // for ascending sort 1 uses 1, -1 uses -1. + // For descending sort, the values are reversed. + // + // Need to handle undefined values. No way around it. + // So have to screen .localeCompare (which is also + // needed) from undefined values. Everywhere, in all + // compares. + // var cmp = 0; if (a.sortkeys[pos] === b.sortkeys[pos]) { cmp = 0; @@ -16959,6 +22368,7 @@ CSL.Registry.Comparifier = function (state, keyset) { } else if ("undefined" === typeof b.sortkeys[pos]) { cmp = sort_directions[pos][0]; } else { + // cmp = a.sortkeys[pos].localeCompare(b.sortkeys[pos]); cmp = sortCompare(a.sortkeys[pos], b.sortkeys[pos]); } if (0 < cmp) { @@ -16979,6 +22389,13 @@ CSL.Registry.Comparifier = function (state, keyset) { return compareKeys(a[1], b[1]); }; }; + + +/** + * Compare two disambiguation tokens by their registry sort order + *Disambiguation lists need to be sorted this way, to + * obtain the correct year-suffix when that's used.
+ */ CSL.Registry.prototype.compareRegistryTokens = function (a, b) { if (a.seq > b.seq) { return 1; @@ -16987,7 +22404,14 @@ CSL.Registry.prototype.compareRegistryTokens = function (a, b) { } return 0; }; + CSL.Registry.prototype.registerAmbigToken = function (akey, id, ambig_config) { + //SNIP-START + if (!this.registry[id]) { + CSL.debug("Warning: unregistered item: itemID=("+id+"), akey=("+akey+")"); + } + //SNIP-END + // Taint if number of names to be included has changed 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]; @@ -16995,6 +22419,7 @@ CSL.Registry.prototype.registerAmbigToken = function (akey, id, ambig_config) { if (new_names_params !== old_names_params) { this.state.tmp.taintedItemIDs[id] = true; } else if (ambig_config.givens[i]) { + // Compare givenses only if the number of names is aligned. for (var j=0,jlen=ambig_config.givens[i].length;j