From 2964021661d7bd2eb832a420bd4c6885ed00defa Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 9 Jul 2019 03:04:12 -0400 Subject: [PATCH] Update citeproc-js to 1.2.13 --- chrome/content/zotero/xpcom/citeproc.js | 7171 ++++++++++++++++++++--- 1 file changed, 6487 insertions(+), 684 deletions(-) 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 or respectively. */ +/*global CSL: true */ + +/** + * A Javascript implementation of the CSL citation formatting language. + * + *

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;i0 || j>1) && !elems[j-1].match(CSL.NOTE_FIELD_REGEXP)) { break; } else { @@ -285,6 +353,7 @@ var CSL = { lines[i] = elems.join(''); } } + // Resplit lines = lines.join('\n').split('\n'); var offset = 0; var names = {}; @@ -307,8 +376,8 @@ var CSL = { Item.type = val; lines[i] = ""; } else if (CSL.DATE_VARIABLES.indexOf(key.replace(/^alt-/, "")) > -1) { - if (allowDateOverride) { - Item[key] = {raw: val}; + if (!Item[key] || allowDateOverride) { + Item[key] = CSL.DateParser.parseDateToArray(val); if (!validFieldsForType || (validFieldsForType[key] && this.isDateString(val))) { lines[i] = ""; } @@ -337,6 +406,7 @@ var CSL = { for (var key in names) { Item[key] = names[key]; } + // Final cleanup for validCslFields only: eliminate blank lines, add blank line to text if (validFieldsForType) { if (lines[offset].trim()) { lines[offset] = '\n' + lines[offset]; @@ -349,11 +419,21 @@ var CSL = { } Item.note = lines.join("\n").trim(); }, + checkPrefixSpaceAppend: function (state, prefix) { if (!prefix) { prefix = ""; } var sp = ""; + // We need the raw string, without decorations + // of any kind. Markup scheme is known, though, so + // markup can be safely stripped at string level. + // + // U+201d = right double quotation mark + // U+2019 = right single quotation mark + // U+00bb = right double angle bracket (guillemet) + // U+202f = non-breaking thin space + // U+00a0 = non-breaking space var test_prefix = prefix.replace(/<[^>]+>/g, "").replace(/["'\u201d\u2019\u00bb\u202f\u00a0 ]+$/g,""); var test_char = test_prefix.slice(-1); if (test_prefix.match(CSL.ENDSWITH_ROMANESQUE_REGEXP)) { @@ -363,9 +443,12 @@ var CSL = { } else if (test_char.match(/[\)\],0-9]/)) { sp = " "; } + // Protect against double spaces, which would trigger an extra, + // explicit, non-breaking space. var prefix = (prefix + sp).replace(/\s+/g, " "); return prefix; }, + checkIgnorePredecessor: function(state, prefix) { var ignorePredecessor = false; var test_prefix = prefix.replace(/<[^>]+>/g, "").replace(/["'\u201d\u2019\u00bb\u202f\u00a0 ]+$/g,""); @@ -376,6 +459,7 @@ var CSL = { } return false; }, + checkSuffixSpacePrepend: function(state, suffix) { if (!suffix) { return ""; @@ -385,48 +469,69 @@ var CSL = { } return suffix; }, + GENDERS: ["masculine", "feminine"], + ERROR_NO_RENDERED_FORM: 1, + PREVIEW: "Just for laughs.", ASSUME_ALL_ITEMS_REGISTERED: 2, + START: 0, END: 1, SINGLETON: 2, + SEEN: 6, SUCCESSOR: 3, SUCCESSOR_OF_SUCCESSOR: 4, SUPPRESS: 5, + SINGULAR: 0, PLURAL: 1, + LITERAL: true, + BEFORE: 1, AFTER: 2, + DESCENDING: 1, ASCENDING: 2, + PRIMARY: 1, SECONDARY: 2, + POSITION_FIRST: 0, POSITION_SUBSEQUENT: 1, POSITION_IBID: 2, POSITION_IBID_WITH_LOCATOR: 3, + POSITION_TEST_VARS: ["position", "first-reference-note-number", "near-note"], + AREAS: ["citation", "citation_sort", "bibliography", "bibliography_sort", "intext"], + CITE_FIELDS: ["first-reference-note-number", "locator", "locator-extra"], + SWAPPING_PUNCTUATION: [".", "!", "?", ":", ","], TERMINAL_PUNCTUATION: [":", ".", ";", "!", "?", " "], + + // update modes NONE: 0, NUMERIC: 1, POSITION: 2, TRIGRAPH: 3, + DATE_PARTS: ["year", "month", "day"], DATE_PARTS_ALL: ["year", "month", "day", "season"], DATE_PARTS_INTERNAL: ["year", "month", "day", "year_end", "month_end", "day_end"], + NAME_PARTS: ["non-dropping-particle", "family", "given", "dropping-particle", "suffix", "literal"], + DISAMBIGUATE_OPTIONS: [ "disambiguate-add-names", "disambiguate-add-givenname", "disambiguate-add-year-suffix" ], + GIVENNAME_DISAMBIGUATION_RULES: [ "all-names", "all-names-with-initials", @@ -434,6 +539,7 @@ var CSL = { "primary-name-with-initials", "by-cite" ], + NAME_ATTRIBUTES: [ "and", "delimiter-precedes-last", @@ -451,28 +557,51 @@ var CSL = { "suffix", "delimiter" ], + PARALLEL_MATCH_VARS: ["container-title"], PARALLEL_TYPES: ["bill","gazette","regulation","legislation","legal_case","treaty","article-magazine","article-journal"], PARALLEL_COLLAPSING_MID_VARSET: ["volume", "issue", "container-title", "section", "collection-number"], + LOOSE: 0, STRICT: 1, TOLERANT: 2, + PREFIX_PUNCTUATION: /[.;:]\s*$/, SUFFIX_PUNCTUATION: /^\s*[.;:,\(\)]/, + NUMBER_REGEXP: /(?:^\d+|\d+$)/, + // + // \u0400-\u042f are cyrillic and extended cyrillic capitals + // this is not fully smart yet. can't do what this was trying to do + // with regexps, actually; we want to identify strings with a leading + // capital letter, and any subsequent capital letters. Have to compare + // locale caps version with existing version, character by character. + // hard stuff, but if it breaks, that's what to do. + // \u0600-\u06ff is Arabic/Persian + // \u200c-\u200e and \u202a-\u202e are special spaces and left-right + // control characters + + + NAME_INITIAL_REGEXP: /^([A-Z\u0e01-\u0e5b\u00c0-\u017f\u0400-\u042f\u0590-\u05d4\u05d6-\u05ff\u0600-\u06ff\u0370\u0372\u0376\u0386\u0388-\u03ab\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03fd-\u03ff])([a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0400-\u052f\u0600-\u06ff\u0370-\u03ff\u1f00-\u1fff]*|)(\.)*/, ROMANESQUE_REGEXP: /[-0-9a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/, ROMANESQUE_NOT_REGEXP: /[^a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/g, STARTSWITH_ROMANESQUE_REGEXP: /^[&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/, ENDSWITH_ROMANESQUE_REGEXP: /[.;:&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]$/, ALL_ROMANESQUE_REGEXP: /^[a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]+$/, + 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]+|[A-Z]+):[^\}]+\}/g, NOTE_FIELD_REGEXP: /^([\-_a-z]+|[A-Z]+):\s*([^\}]+)$/, + PARTICLE_GIVEN_REGEXP: /^([^ ]+(?:\u02bb |\u2019 | |\' ) *)(.+)$/, PARTICLE_FAMILY_REGEXP: /^([^ ]+(?:\-|\u02bb|\u2019| |\') *)(.+)$/, + DISPLAY_CLASSES: ["block", "left-margin", "right-inline", "indent"], + NAME_VARIABLES: [ "author", "collection-editor", @@ -507,6 +636,7 @@ var CSL = { "call-number", "chapter-number", "collection-number", + "division", "edition", "page", "issue", @@ -516,9 +646,16 @@ var CSL = { "number-of-pages", "number-of-volumes", "volume", + // "section", ??? add this? "supplement", "citation-number" ], + //var x = new Array(); + //x = x.concat(["title","container-title","issued","page"]); + //x = x.concat(["locator","collection-number","original-date"]); + //x = x.concat(["reporting-date","decision-date","filing-date"]); + //x = x.concat(["revision-date"]); + //NUMERIC_VARIABLES = x.slice(); DATE_VARIABLES: [ "locator-date", "issued", @@ -536,13 +673,14 @@ var CSL = { "container-title" ], TITLE_FIELD_SPLITS: function(seg) { - var keys = ["title", "short", "main", "sub"]; + var keys = ["title", "short", "main", "sub", "subjoin"]; var ret = {}; for (var i=0,ilen=keys.length;i -1) { + vals[title.subjoin] = narrowSpace + ": "; + } + if (vals[title.subjoin].indexOf("-") > -1 || vals[title.subjoin].indexOf("—") > -1) { + vals[title.subjoin] = "—"; } } if (lang) { @@ -617,7 +810,14 @@ var CSL = { } } }, + titlecaseSentenceOrNormal: function(state, Item, seg, lang, sentenceCase) { + // Hold on here. + // What is seg here? + // It's ... either "" or "container-". Which is ugly, but works. + // But this ALWAYS returns the full title, never short. + // So sentence-casing cannot be applied to short. + // Goes unnoticed because forced sentence-casing almost never appears in styles. var title = CSL.TITLE_FIELD_SPLITS(seg); var vals = {}; if (lang && Item.multi) { @@ -630,13 +830,18 @@ var CSL = { if (Item.multi._keys[title.sub]) { vals[title.sub] = Item.multi._keys[title.sub][lang]; } + if (Item.multi._keys[title.subjoin]) { + vals[title.subjoin] = Item.multi._keys[title.subjoin][lang]; + } } else { vals[title.title] = Item[title.title]; vals[title.main] = Item[title.main]; vals[title.sub] = Item[title.sub]; + vals[title.subjoin] = Item[title.subjoin]; } if (vals[title.main] && vals[title.sub]) { var mainTitle = vals[title.main]; + var subJoin = vals[title.subjoin]; var subTitle = vals[title.sub]; if (sentenceCase) { mainTitle = CSL.Output.Formatters.sentence(state, mainTitle); @@ -644,17 +849,41 @@ var CSL = { } else if (state.opt.development_extensions.uppercase_subtitles) { subTitle = CSL.Output.Formatters["capitalize-first"](state, subTitle); } - return [mainTitle, subTitle].join(vals[title.title].slice(mainTitle.length, -1 * subTitle.length)); - } else { + return [mainTitle, subJoin, subTitle].join(""); + } else if (vals[title.title]) { if (sentenceCase) { return CSL.Output.Formatters.sentence(state, vals[title.title]); + } else if (state.opt.development_extensions.uppercase_subtitles) { + // Split and apply everywhere. + var splits = CSL.TITLE_SPLIT(vals[title.title]); + for (var i=0,ilen=splits.length; i -1 || splits[i].indexOf("—") > -1) { + splits[i] = "—"; + } + } + vals[title.title] = splits.join(""); + return vals[title.title]; } else { return vals[title.title]; } + } else { + return ""; } }, + getSafeEscape: function(state) { if (["bibliography", "citation"].indexOf(state.tmp.area) > -1) { + // Callback to apply thin space hack + // Callback to force LTR/RTL on parens and braces + // XXX Is this really necessary? var callbacks = []; if (state.opt.development_extensions.thin_non_breaking_space_html_hack && state.opt.mode === "html") { callbacks.push(function (txt) { @@ -675,7 +904,9 @@ var CSL = { return function (txt) { return txt; }; } }, + SKIP_WORDS: ["about","above","across","afore","after","against","al", "along","alongside","amid","amidst","among","amongst","anenst","apropos","apud","around","as","aside","astride","at","athwart","atop","barring","before","behind","below","beneath","beside","besides","between","beyond","but","by","circa","despite","down","during","et", "except","for","forenenst","from","given","in","inside","into","lest","like","modulo","near","next","notwithstanding","of","off","on","onto","out","over","per","plus","pro","qua","sans","since","than","through"," thru","throughout","thruout","till","to","toward","towards","under","underneath","until","unto","up","upon","versus","vs.","v.","vs","v","via","vis-à-vis","with","within","without","according to","ahead of","apart from","as for","as of","as per","as regards","aside from","back to","because of","close to","due to","except for","far from","inside of","instead of","near to","next to","on to","out from","out of","outside of","prior to","pursuant to","rather than","regardless of","such as","that of","up to","where as","or", "yet", "so", "for", "and", "nor", "a", "an", "the", "de", "d'", "von", "van", "c", "ca"], + FORMAT_KEY_SEQUENCE: [ "@strip-periods", "@font-style", @@ -685,6 +916,7 @@ var CSL = { "@vertical-align", "@quotes" ], + INSTITUTION_KEYS: [ "font-style", "font-variant", @@ -692,6 +924,7 @@ var CSL = { "text-decoration", "text-case" ], + SUFFIX_CHARS: "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z", ROMAN_NUMERALS: [ [ "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" ], @@ -699,6 +932,7 @@ var CSL = { [ "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" ], [ "", "m", "mm", "mmm", "mmmm", "mmmmm"] ], + LANGS: { "af-ZA":"Afrikaans", "ar":"Arabic", @@ -749,6 +983,7 @@ var CSL = { "zh-CN":"Chinese (CN)", "zh-TW":"Chinese (TW)" }, + LANG_BASES: { af: "af_ZA", ar: "ar", @@ -793,6 +1028,7 @@ var CSL = { vi: "vi_VN", zh: "zh_CN" }, + SUPERSCRIPTS: { "\u00AA": "\u0061", "\u00B2": "\u0032", @@ -900,6 +1136,7 @@ var CSL = { "\u06E6": "\u064A" }, SUPERSCRIPTS_REGEXP: new RegExp("[\u00AA\u00B2\u00B3\u00B9\u00BA\u02B0\u02B1\u02B2\u02B3\u02B4\u02B5\u02B6\u02B7\u02B8\u02E0\u02E1\u02E2\u02E3\u02E4\u1D2C\u1D2D\u1D2E\u1D30\u1D31\u1D32\u1D33\u1D34\u1D35\u1D36\u1D37\u1D38\u1D39\u1D3A\u1D3C\u1D3D\u1D3E\u1D3F\u1D40\u1D41\u1D42\u1D43\u1D44\u1D45\u1D46\u1D47\u1D48\u1D49\u1D4A\u1D4B\u1D4C\u1D4D\u1D4F\u1D50\u1D51\u1D52\u1D53\u1D54\u1D55\u1D56\u1D57\u1D58\u1D59\u1D5A\u1D5B\u1D5C\u1D5D\u1D5E\u1D5F\u1D60\u1D61\u2070\u2071\u2074\u2075\u2076\u2077\u2078\u2079\u207A\u207B\u207C\u207D\u207E\u207F\u2120\u2122\u3192\u3193\u3194\u3195\u3196\u3197\u3198\u3199\u319A\u319B\u319C\u319D\u319E\u319F\u02C0\u02C1\u06E5\u06E6]", "g"), + UPDATE_GROUP_CONTEXT_CONDITION: function (state, termtxt, valueTerm) { if (state.tmp.group_context.tip.condition) { if (state.tmp.group_context.tip.condition.test) { @@ -938,6 +1175,8 @@ var CSL = { } } } else { + // If not inside a conditional group, raise numeric flag + // if and only if the current term string ends in a number. if (termtxt.slice(-1).match(/[0-9]/)) { state.tmp.just_did_number = true; } else { @@ -945,28 +1184,75 @@ var CSL = { } } }, + SYS_OPTIONS: [ "prioritize_disambiguate_condition", "csl_reverse_lookup_support", "main_title_from_short_title", "uppercase_subtitles" - ] + ], + + TITLE_SPLIT_REGEXP: (function() { + var splits = [ + "\\.\\s+", + "\\!\\s+", + "\\?\\s+", + "\\s*::*\\s+", + "\\s*—\\s*", + "\\s+\\-\\s+", + "\\s*\\-\\-\\-*\\s*" + ] + return { + match: new RegExp("(" + splits.join("|") + ")", "g"), + matchfirst: new RegExp("^(" + splits.join("|") + ")"), + split: new RegExp("(?:" + splits.join("|") + ")") + } + })(), + + TITLE_SPLIT: function(str) { + if (!str) { + return str; + } + var m = str.match(CSL.TITLE_SPLIT_REGEXP.match); + var lst = str.split(CSL.TITLE_SPLIT_REGEXP.split); + for (var i=lst.length-2; i>-1; i--) { + lst[i] = lst[i].trim(); + if (lst[i] && lst[i].slice(-1).toLowerCase() !== lst[i].slice(-1)) { + // recombine + lst[i] = lst[i] + m[i] + lst[i+1]; + lst = lst.slice(0, i+1).concat(lst.slice(i+2)) + } else { + // merge + lst = lst.slice(0, i+1).concat([m[i]]).concat(lst.slice(i+1)) + } + } + return lst; + } }; -if ("undefined" === typeof console) { - CSL.debug = function (str) { - dump("CSL: " + str + "\n"); - }; - CSL.error = function (str) { - dump("CSL error: " + str + "\n"); - }; -} else { - CSL.debug = function (str) { - console.log("CSL: " + str); - }; - CSL.error = function (str) { - console.log("CSL error: " + str); - }; -} + +/** + * Functions for parsing an XML object converted to JSON. + */ + +/* + Style and locale JSON should be formatted as follows. Note that + an empty literal should be set as an explicit empty strings within + children:[] + + { + name:"term", + children:[ + "" + ], + attrs:{ + name:"author" + } + } + + The following script will generate correctly formatted JSON + from a CSL style or locale file: +*/ + CSL.XmlJSON = function (dataObj) { this.dataObj = dataObj; this.institution = { @@ -988,9 +1274,18 @@ CSL.XmlJSON = function (dataObj) { ] }; }; + +/** + * No need for cleaning with native JSON. + */ CSL.XmlJSON.prototype.clean = function (json) { return json; }; + + +/** + * Methods to call on a node. + */ CSL.XmlJSON.prototype.getStyleId = function (myjson, styleName) { var tagName = 'id'; if (styleName) { @@ -1010,25 +1305,37 @@ CSL.XmlJSON.prototype.getStyleId = function (myjson, styleName) { } return ret; }; + CSL.XmlJSON.prototype.children = function (myjson) { + //print("children()"); if (myjson && myjson.children.length) { return myjson.children.slice(); } else { return false; } }; + CSL.XmlJSON.prototype.nodename = function (myjson) { + //print("nodename()"); return myjson ? myjson.name : null; }; + CSL.XmlJSON.prototype.attributes = function (myjson) { + //print("attributes()"); var ret = {}; for (var attrname in myjson.attrs) { ret["@"+attrname] = myjson.attrs[attrname]; } return ret; }; + + CSL.XmlJSON.prototype.content = function (myjson) { + //print("content()"); + // xmldom.js and xmle4x.js have "undefined" as default var ret = ""; + // This only catches content at first level, but that is good enough + // for us. if (!myjson || !myjson.children) { return ret; } @@ -1039,15 +1346,23 @@ CSL.XmlJSON.prototype.content = function (myjson) { } return ret; }; + + CSL.XmlJSON.prototype.namespace = {} + CSL.XmlJSON.prototype.numberofnodes = function (myjson) { + //print("numberofnodes()"); if (myjson && "number" == typeof myjson.length) { return myjson.length; } else { return 0; } }; + +// getAttributeName() removed. Looks like it was not being used. + CSL.XmlJSON.prototype.getAttributeValue = function (myjson,name,namespace) { + //print("getAttributeValue()"); var ret = ""; if (namespace) { name = namespace+":"+name; @@ -1063,11 +1378,14 @@ CSL.XmlJSON.prototype.getAttributeValue = function (myjson,name,namespace) { } return ret; } + CSL.XmlJSON.prototype.getNodeValue = function (myjson,name) { + //print("getNodeValue()"); var ret = ""; if (name){ for (var i=0, ilen=myjson.children.length; i < ilen; i += 1) { if (myjson.children[i].name === name) { + // This will always be Object() unless empty if (myjson.children[i].children.length) { ret = myjson.children[i]; } else { @@ -1078,23 +1396,30 @@ CSL.XmlJSON.prototype.getNodeValue = function (myjson,name) { } else if (myjson) { ret = myjson; } + // Just being careful here, following the former DOM code. The JSON object we receive + // for this should be fully normalized. if (ret && ret.children && ret.children.length == 1 && "string" === typeof ret.children[0]) { ret = ret.children[0]; } return ret; } + CSL.XmlJSON.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myjson,nodename,partname,attrname,val) { + //print("setAttributeOnNodeIdentifiedByNameAttribute()"); var pos, len, xml, nodes, node; if (attrname.slice(0,1) === '@'){ attrname = attrname.slice(1); } + // In the one place this is used in citeproc-js code, it doesn't need to recurse. for (var i=0,ilen=myjson.children.length; i -1) { + continue; + } + var child = node.childNodes.item(j); + var subskippers = []; + for (var k = 0, klen = child.childNodes.length; k < klen; k += 1) { + if (child.childNodes.item(k).nodeType !== 1) { + subskippers.push(k); + } + } + if (child.childNodes.length - subskippers.length === 0) { + twovars.push(child.getAttribute('variable')); + if (child.getAttribute('suffix') + || child.getAttribute('prefix')) { + twovars = []; + break; + } + } + } + if (twovars.indexOf("publisher") > -1 && twovars.indexOf("publisher-place") > -1) { + node.setAttribute('has-publisher-and-publisher-place', true); + } + } + } +}; +*/ + CSL.XmlJSON.prototype.isChildOfSubstitute = function(parents) { if (parents.length > 0) { var myparents = parents.slice(); @@ -1231,11 +1617,13 @@ CSL.XmlJSON.prototype.isChildOfSubstitute = function(parents) { } return false; }; + CSL.XmlJSON.prototype.addMissingNameNodes = function(myjson,parents) { if (!parents) { parents = []; } if (myjson.name === "names") { + // Trawl through children to decide whether a name node is needed here if (!this.isChildOfSubstitute(parents)) { var addName = true; for (var i=0,ilen=myjson.children.length;i[ ]+<").replace(/<\!--.*?-->/g, ""); var lst = str.split("><"); @@ -1364,6 +1773,9 @@ CSL.parseXml = function(str) { } } lst = lst.slice(stylePos); + // Combine open/close elements for empty terms, + // so that they will be passed through correctly + // as empty strings. for (var i=lst.length-2;i>-1;i--) { if (lst[i].slice(1).indexOf("<") === -1) { var stub = lst[i].slice(0, 5); @@ -1384,6 +1796,7 @@ CSL.parseXml = function(str) { } return lst; } + function _decodeHtmlEntities(str) { return str .split("&").join("&") @@ -1398,6 +1811,7 @@ CSL.parseXml = function(str) { return String.fromCharCode(num); }); } + function _getAttributes(elem) { var m = elem.match(/([^\'\"= ]+)=(?:\"[^\"]*\"|\'[^\']*\')/g); if (m) { @@ -1407,16 +1821,20 @@ CSL.parseXml = function(str) { } return m; } + function _getAttribute(elem, attr) { var rex = RegExp('^.*[ ]+' + attr + '=(\"(?:[^\"]*)\"|\'(?:[^\']*)\').*$'); var m = elem.match(rex); return m ? m[1].slice(1, -1) : null; } + function _getTagName(elem) { var rex = RegExp("^<([^ />]+)"); var m = elem.match(rex); return m ? m[1] : null; } + + function _castObjectFromOpeningTag(elem) { var obj = {}; obj.name = _getTagName(elem); @@ -1434,44 +1852,60 @@ CSL.parseXml = function(str) { obj.children = []; return obj; } + function _extractTextFromCompositeElement(elem) { var m = elem.match(/^.*>([^<]*)<.*$/); return _decodeHtmlEntities(m[1]); } + function _appendToChildren(obj) { _stack.slice(-1)[0].push(obj); } + function _extendStackWithNewChildren(obj) { _stack.push(obj.children); } + function processElement(elem) { var obj; if (elem.slice(1).indexOf('<') > -1) { + // withtext var tag = elem.slice(0, elem.indexOf('>')+1); obj = _castObjectFromOpeningTag(tag); obj.children = [_extractTextFromCompositeElement(elem)]; _appendToChildren(obj); } else if (elem.slice(-2) === '/>') { + // singleton obj = _castObjectFromOpeningTag(elem); + // Empty term as singleton if (_getTagName(elem) === 'term') { obj.children.push(''); } _appendToChildren(obj); } else if (elem.slice(0, 2) === ' 0) @@ -1542,9 +1986,15 @@ CSL.XmlDOM = function (dataObj) { case 3: case 4: case 8: + // Drop comments on the floor as well. + //return doc.createTextNode(node.nodeValue); + //break; } }; this.parser = new DOMParser(); + + // This seems horribly tormented, but there might be a reason for it. + // Perhaps this was the only way I found to get namespacing to work ... ? var str = ""; var inst_doc = this.parser.parseFromString(str, "text/xml"); var inst_node = inst_doc.getElementsByTagName("institution"); @@ -1553,6 +2003,11 @@ CSL.XmlDOM = function (dataObj) { this.institutionpart = inst_part_node.item(0); this.ns = "http://purl.org/net/xbiblio/csl"; }; + +/** + * No need for cleaning with the DOM, I think. This will probably just be a noop. + * But first, let's get XML mode switching up and running. + */ CSL.XmlDOM.prototype.clean = function (xml) { xml = xml.replace(/<\?[^?]+\?>/g, ""); xml = xml.replace(/]+>/g, ""); @@ -1561,6 +2016,11 @@ CSL.XmlDOM.prototype.clean = function (xml) { xml = xml.replace(/^\n*/, ""); return xml; }; + + +/** + * Methods to call on a node. + */ CSL.XmlDOM.prototype.getStyleId = function (myxml, styleName) { var text = ""; var tagName = "id"; @@ -1572,16 +2032,20 @@ CSL.XmlDOM.prototype.getStyleId = function (myxml, styleName) { node = node.item(0); } if (node) { + // W3C conformant browsers text = node.textContent; } if (!text) { + // Opera, IE 6 & 7 text = node.innerText; } if (!text) { + // Safari text = node.innerHTML; } return text; }; + CSL.XmlDOM.prototype.children = function (myxml) { var children, pos, len, ret; if (myxml) { @@ -1597,10 +2061,12 @@ CSL.XmlDOM.prototype.children = function (myxml) { return []; } }; + CSL.XmlDOM.prototype.nodename = function (myxml) { var ret = myxml.nodeName; return ret; }; + CSL.XmlDOM.prototype.attributes = function (myxml) { var ret, attrs, attr, key, xml, pos, len; ret = new Object(); @@ -1613,6 +2079,8 @@ CSL.XmlDOM.prototype.attributes = function (myxml) { } return ret; }; + + CSL.XmlDOM.prototype.content = function (myxml) { var ret; if ("undefined" != typeof myxml.textContent) { @@ -1624,9 +2092,12 @@ CSL.XmlDOM.prototype.content = function (myxml) { } return ret; }; + + CSL.XmlDOM.prototype.namespace = { "xml":"http://www.w3.org/XML/1998/namespace" } + CSL.XmlDOM.prototype.numberofnodes = function (myxml) { if (myxml) { return myxml.length; @@ -1634,10 +2105,12 @@ CSL.XmlDOM.prototype.numberofnodes = function (myxml) { return 0; } }; + CSL.XmlDOM.prototype.getAttributeName = function (attr) { var ret = attr.name; return ret; } + CSL.XmlDOM.prototype.getAttributeValue = function (myxml,name,namespace) { var ret = ""; if (namespace) { @@ -1648,6 +2121,10 @@ CSL.XmlDOM.prototype.getAttributeValue = function (myxml,name,namespace) { } return ret; } + +// +// Can't this be, you know ... simplified? +// CSL.XmlDOM.prototype.getNodeValue = function (myxml,name) { var ret = null; if (name){ @@ -1676,6 +2153,7 @@ CSL.XmlDOM.prototype.getNodeValue = function (myxml,name) { } return ret; } + CSL.XmlDOM.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myxml,nodename,partname,attrname,val) { var pos, len, xml, nodes, node; if (attrname.slice(0,1) === '@'){ @@ -1690,6 +2168,7 @@ CSL.XmlDOM.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myx node.setAttribute(attrname, val); } } + CSL.XmlDOM.prototype.deleteNodeByNameAttribute = function (myxml,val) { var pos, len, node, nodes; nodes = myxml.childNodes; @@ -1703,22 +2182,30 @@ CSL.XmlDOM.prototype.deleteNodeByNameAttribute = function (myxml,val) { } } } + CSL.XmlDOM.prototype.deleteAttribute = function (myxml,attr) { myxml.removeAttribute(attr); } + CSL.XmlDOM.prototype.setAttribute = function (myxml,attr,val) { if (!myxml.ownerDocument) { myxml = myxml.firstChild; } + // "unknown" to satisfy IE8, which crashes when setAttribute + // is checked directly as a property, and report its type as + // "unknown". + // Many thanks to Phil Lord for tracing the cause of the fault. if (["function", "unknown"].indexOf(typeof myxml.setAttribute) > -1) { myxml.setAttribute(attr, val); } return false; } + CSL.XmlDOM.prototype.nodeCopy = function (myxml) { var cloned_node = myxml.cloneNode(true); return cloned_node; } + CSL.XmlDOM.prototype.getNodesByName = function (myxml,name,nameattrval) { var ret, nodes, node, pos, len; ret = []; @@ -1726,18 +2213,21 @@ CSL.XmlDOM.prototype.getNodesByName = function (myxml,name,nameattrval) { for (pos = 0, len = nodes.length; pos < len; pos += 1) { node = nodes.item(pos); if (nameattrval && !(this.hasAttributes(node) && node.getAttribute("name") == nameattrval)) { +// if (nameattrval && !(this.attributes && node.attributes.name && node.attributes.name.value == nameattrval)) { continue; } ret.push(node); } return ret; } + CSL.XmlDOM.prototype.nodeNameIs = function (myxml,name) { if (name == myxml.nodeName) { return true; } return false; } + CSL.XmlDOM.prototype.makeXml = function (myxml) { var ret, topnode; if (!myxml) { @@ -1747,12 +2237,14 @@ CSL.XmlDOM.prototype.makeXml = function (myxml) { var nodetree = this.parser.parseFromString(myxml, "application/xml"); return nodetree.firstChild; }; + CSL.XmlDOM.prototype.insertChildNodeAfter = function (parent,node,pos,datexml) { var myxml, xml; myxml = this.importNode(node.ownerDocument, datexml); parent.replaceChild(myxml, node); return parent; }; + CSL.XmlDOM.prototype.insertPublisherAndPlace = function(myxml) { var group = myxml.getElementsByTagName("group"); for (var i = 0, ilen = group.length; i < ilen; i += 1) { @@ -1791,6 +2283,7 @@ CSL.XmlDOM.prototype.insertPublisherAndPlace = function(myxml) { } } }; + CSL.XmlDOM.prototype.isChildOfSubstitute = function(node) { if (node.parentNode) { if (node.parentNode.tagName.toLowerCase() === "substitute") { @@ -1801,6 +2294,7 @@ CSL.XmlDOM.prototype.isChildOfSubstitute = function(node) { } return false; }; + CSL.XmlDOM.prototype.addMissingNameNodes = function(myxml) { var nameslist = myxml.getElementsByTagName("names"); for (var i = 0, ilen = nameslist.length; i < ilen; i += 1) { @@ -1808,12 +2302,15 @@ CSL.XmlDOM.prototype.addMissingNameNodes = function(myxml) { var namelist = names.getElementsByTagName("name"); if ((!namelist || namelist.length === 0) && !this.isChildOfSubstitute(names)) { + var doc = names.ownerDocument; var name = doc.createElement("name"); names.appendChild(name); } } }; + + CSL.XmlDOM.prototype.addInstitutionNodes = function(myxml) { var names, thenames, institution, theinstitution, theinstitutionpart, name, thename, xml, pos, len; names = myxml.getElementsByTagName("names"); @@ -1851,6 +2348,8 @@ CSL.XmlDOM.prototype.addInstitutionNodes = function(myxml) { } } }; + + CSL.XmlDOM.prototype.flagDateMacros = function(myxml) { var pos, len, thenode, thedate; var nodes = myxml.getElementsByTagName("macro"); @@ -1862,6 +2361,10 @@ CSL.XmlDOM.prototype.flagDateMacros = function(myxml) { } } }; + + +/*global CSL: true */ + CSL.setupXml = function(xmlObject) { var dataObj = {}; var parser = null; @@ -1870,26 +2373,34 @@ CSL.setupXml = function(xmlObject) { xmlObject = xmlObject.replace("^\uFEFF", "") .replace(/^\s+/, ""); if (xmlObject.slice(0, 1) === "<") { + // Assume serialized XML dataObj = CSL.parseXml(xmlObject); } else { + // Assume serialized JSON dataObj = JSON.parse(xmlObject); } parser = new CSL.XmlJSON(dataObj); } else if ("undefined" !== typeof xmlObject.getAttribute) { + // Assume DOM instance parser = new CSL.XmlDOM(xmlObject); } else if ("undefined" !== typeof xmlObject.toXMLString) { + // Assume E4X object parser = new CSL.XmlE4X(xmlObject); } else { + // Assume JS object parser = new CSL.XmlJSON(xmlObject); } } else { CSL.error("unable to parse XML input"); } if (!parser) { - throw "citeproc-js error: unable to parse CSL style or locale object"; + CSL.error("citeproc-js error: unable to parse CSL style or locale object"); } return parser; }; + +/*global CSL: true */ + CSL.getSortCompare = function (default_locale) { if (CSL.stringCompare) { return CSL.stringCompare; @@ -1900,10 +2411,16 @@ CSL.getSortCompare = function (default_locale) { ignorePunctuation:true, numeric:true }; + // In order, attempt the following: + // (1) Set locale collation from processor language + // (2) Use localeCompare() if (!default_locale) { default_locale = "en-US"; } strcmp = function (a, b) { + //var ret = a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase(),default_locale,strcmp_opts); + // print(ret+' ('+a+') :: ('+b+')'); + //return ret; return a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase(),default_locale,strcmp_opts); }; var stripPunct = function (str) { @@ -1928,17 +2445,24 @@ CSL.getSortCompare = function (default_locale) { }; return sortCompare; }; + +/*global CSL: true */ + CSL.ambigConfigDiff = function(a, b) { var pos, len, ppos, llen; + // return of true means the ambig configs differ if (a.names.length !== b.names.length) { + //print(" (1)"); return 1; } else { for (pos = 0, len = a.names.length; pos < len; pos += 1) { if (a.names[pos] !== b.names[pos]) { + //print(" (2) "+a.names[pos]+" "+b.names[pos]); return 1; } else { for (ppos = 0, llen = a.givens[pos]; ppos < llen; ppos += 1) { if (a.givens[pos][ppos] !== b.givens[pos][ppos]) { + //print(" (3): "+a.givens[pos][ppos]+" "+b.givens[pos][ppos]+" "+pos+"/"+ppos+" "+b.givens[pos]); return 1; } } @@ -1946,13 +2470,16 @@ CSL.ambigConfigDiff = function(a, b) { } } if (a.disambiguate != b.disambiguate) { + //print(" (4) "+a.disambiguate+" "+b.disambiguate); return 1; } if (a.year_suffix !== b.year_suffix) { + //print(" (5) "+a.year_suffix+" "+b.year_suffix); return 1; } return 0; }; + CSL.cloneAmbigConfig = function (config, oldconfig) { var i, ilen, j, jlen, param; var ret = {}; @@ -1962,15 +2489,21 @@ CSL.cloneAmbigConfig = function (config, oldconfig) { ret.disambiguate = false; for (i = 0, ilen = config.names.length; i < ilen; i += 1) { param = config.names[i]; + // Fixes update bug affecting plugins, without impacting + // efficiency with update of large numbers of year-suffixed + // items. ret.names[i] = param; } for (i = 0, ilen = config.givens.length; i < ilen; i += 1) { param = []; for (j = 0, jlen = config.givens[i].length; j < jlen; j += 1) { + // condition at line 312 of disambiguate.js protects against negative + // values of j param.push(config.givens[i][j]); } ret.givens.push(param); } + // XXXX Is this necessary at all? if (oldconfig) { ret.year_suffix = oldconfig.year_suffix; ret.disambiguate = oldconfig.disambiguate; @@ -1980,6 +2513,10 @@ CSL.cloneAmbigConfig = function (config, oldconfig) { } return ret; }; + +/** + * Return current base configuration for disambiguation + */ CSL.getAmbigConfig = function () { var config, ret; config = this.tmp.disambig_request; @@ -1989,17 +2526,52 @@ CSL.getAmbigConfig = function () { var ret = CSL.cloneAmbigConfig(config); return ret; }; + +/** + * Return max values for disambiguation + */ CSL.getMaxVals = function () { return this.tmp.names_max.mystack.slice(); }; + +/** + * Return min value for disambiguation + */ CSL.getMinVal = function () { return this.tmp["et-al-min"]; }; + +/*global CSL: true */ + +/* For node execution pretty-printing (see below) */ + +/* +var INDENT = ""; +*/ + CSL.tokenExec = function (token, Item, item) { + // Called on state object var next, maybenext, exec, debug; debug = false; next = token.next; maybenext = false; + + /* Pretty-print node executions */ + + /* + if (["if", "else-if", "else"].indexOf(token.name) === -1) { + if (token.tokentype == 1) { + INDENT = INDENT.slice(0, -2); + } + } + this.sys.print(INDENT + "---> Token: " + token.name + " (" + token.tokentype + ") in " + this.tmp.area + ", " + this.output.current.mystack.length); + if (["if", "else-if", "else"].indexOf(token.name) === -1) { + if (token.tokentype == 0) { + INDENT += " "; + } + } + */ + var record = function (result) { if (result) { this.tmp.jump.replace("succeed"); @@ -2019,13 +2591,28 @@ CSL.tokenExec = function (token, Item, item) { next = maybenext; } } + //SNIP-START + if (debug) { + CSL.debug(token.name + " (" + token.tokentype + ") ---> done"); + } + //SNIP-END return next; }; + +/** + * Macro expander. + *

Called 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}.

+ * @param {Object} state The state object returned by {@link CSL.Engine}. + * @param {Int} tokentype A CSL namespace constant (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; i lst[i].length || abbrevLength > this.monthSets[j][k].length) { CSL.debug("unable to disambiguate month string in date parser: "+lst[i]); break; } else { + // Mark both new entry and existing abbrev for extension abbrevLength += 1; } } @@ -2284,11 +2971,14 @@ CSL.DateParser = function () { } } } + // Insert here if (!skip) { this.monthSets[i].push(lst[i]); this.monthAbbrevs[i].push(lst[i].slice(0, insert)); } } + + // Compose this.monthRexes = []; this.monthRexStrs = []; for (var i=0,ilen=this.monthAbbrevs.length; i -1) { this._parseNumericDate(thedate, dateDelim, suff, element); continue; } + // + // If it's an obvious year, record it. + // if (element.match(/[0-9]{4}/)) { thedate[("year" + suff)] = element.replace(/^0*/, ""); continue; } + // + // If it's a fuzzy marker, record it. + // if (element === "~" || element === "?" || element === "c" || element.match(/^cir/)) { thedate.circa = true; } + // + // If it's a month, record it. + // for (var k=0,klen=this.monthRexes.length; k -1) { thedate.year = (thedate.year * -1); } return thedate; }; + this.parseDateToArray = function(txt) { return this.convertDateObjectToArray(this.parseDateToObject(txt)); }; + this.parseDateToString = function(txt) { return this.convertDateObjectToString(this.parseDateToObject(txt)); }; + this.parse = function(txt) { return this.parseDateToObject(txt); }; + + /* + + * Setup + */ + this.setOrderMonthDay(); this.resetDateParserMonths(); }; CSL.DateParser = new CSL.DateParser(); + +/*global CSL: true */ + CSL.Engine = function (sys, style, lang, forceLang) { var attrs, langspec; this.processor_version = CSL.PROCESSOR_VERSION; this.csl_version = "1.0"; this.sys = sys; + if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true Object.defineProperty(Object, "assign", { value: function assign(target) { // .length of function is 2 'use strict'; if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; + if (nextSource != null) { // Skip over if undefined or null for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } @@ -2595,9 +3394,14 @@ CSL.Engine = function (sys, style, lang, forceLang) { configurable: true }); } + + // XXX This may be excess code. Given the normalization performed on + // XXX the output queue before variableWrapper() is run, a single + // XXX space should be the most cruft that we ever see before a variable. if (sys.variableWrapper) { CSL.VARIABLE_WRAPPER_PREPUNCT_REX = new RegExp('^([' + [" "].concat(CSL.SWAPPING_PUNCTUATION).join("") + ']*)(.*)'); } + // XXXX This should be restored -- temporarily suspended for testing of JSON style support. if (CSL.retrieveStyleModule) { this.sys.retrieveStyleModule = CSL.retrieveStyleModule; } @@ -2609,28 +3413,46 @@ CSL.Engine = function (sys, style, lang, forceLang) { } this.sys.AbbreviationSegments = CSL.AbbreviationSegments; this.parallel = new CSL.Parallel(this); + //this.parallel.use_parallels = true; + this.transform = new CSL.Transform(this); + // true or false this.setParseNames = function (val) { this.opt['parse-names'] = val; }; + this.opt = new CSL.Engine.Opt(); this.tmp = new CSL.Engine.Tmp(); this.build = new CSL.Engine.Build(); this.fun = new CSL.Engine.Fun(this); + this.configure = new CSL.Engine.Configure(); + // Build citation before citation_sort in order to pick up + // state.opt.update_mode, needed it determine whether + // a grouped sort should be performed. this.citation_sort = new CSL.Engine.CitationSort(); this.bibliography_sort = new CSL.Engine.BibliographySort(); this.citation = new CSL.Engine.Citation(this); this.bibliography = new CSL.Engine.Bibliography(); this.intext = new CSL.Engine.InText(); + this.output = new CSL.Output.Queue(this); + + //this.render = new CSL.Render(this); + // + // This latter queue is used for formatting date chunks + // before they are folded back into the main queue. + // this.dateput = new CSL.Output.Queue(this); + this.cslXml = CSL.setupXml(style); + for (var i in CSL.SYS_OPTIONS) { var option = CSL.SYS_OPTIONS[i]; if ("boolean" === typeof this.sys[option]) { this.opt.development_extensions[option] = this.sys[option]; } + } if (this.opt.development_extensions.uppercase_subtitles) { this.opt.development_extensions.main_title_from_short_title = true; @@ -2651,6 +3473,7 @@ CSL.Engine = function (sys, style, lang, forceLang) { }; this.setCslNodeIds(this.cslXml.dataObj, "style"); } + // Preprocessing ops for the XML input this.cslXml.addMissingNameNodes(this.cslXml.dataObj); this.cslXml.addInstitutionNodes(this.cslXml.dataObj); this.cslXml.insertPublisherAndPlace(this.cslXml.dataObj); @@ -2659,12 +3482,23 @@ CSL.Engine = function (sys, style, lang, forceLang) { if ("undefined" === typeof attrs["@sort-separator"]) { this.cslXml.setAttribute(this.cslXml.dataObj, "sort-separator", ", "); } + // This setting does the right thing and seems not to be side-effects this.opt["initialize-with-hyphen"] = true; + + // Locale resolution + // + // (1) Get three locale strings + // -- default-locale (stripped) + // -- processor-locale + // -- en_US + this.setStyleAttributes(); + this.opt.xclass = this.cslXml.getAttributeValue(this.cslXml.dataObj, "class"); this.opt["class"] = this.opt.xclass; this.opt.styleID = this.cslXml.getStyleId(this.cslXml.dataObj); this.opt.styleName = this.cslXml.getStyleId(this.cslXml.dataObj, true); + if (this.opt.version.slice(0,4) === "1.1m") { this.opt.development_extensions.static_statute_locator = true; this.opt.development_extensions.handle_parallel_articles = true; @@ -2673,6 +3507,10 @@ CSL.Engine = function (sys, style, lang, forceLang) { this.opt.development_extensions.require_explicit_legal_case_title_short = true; this.opt.development_extensions.force_jurisdiction = true; } + // We seem to have two language specs flying around: + // this.opt["default-locale"], and this.opt.lang + // Keeping them aligned for safety's sake, pending + // eventual cleanup. if (lang) { lang = lang.replace("_", "-"); lang = CSL.normalizeLocaleStr(lang); @@ -2703,43 +3541,82 @@ CSL.Engine = function (sys, style, lang, forceLang) { if (!this.opt["default-locale-sort"]) { this.opt["default-locale-sort"] = this.opt["default-locale"][0]; } + // Test processor against JS engine locale mess to find a field separator that works if ('dale|'.localeCompare('daleb', this.opt["default-locale-sort"]) > -1) { this.opt.sort_sep = "@"; } else { this.opt.sort_sep = "|"; } this.localeConfigure(langspec); + + // Build skip-word regexp function makeRegExp(lst) { var lst = lst.slice(); var ret = new RegExp( "(?:(?:[?!:]*\\s+|-|^)(?:" + lst.join("|") + ")(?=[!?:]*\\s+|-|$))", "g"); return ret; } this.locale[this.opt.lang].opts["skip-words-regexp"] = makeRegExp(this.locale[this.opt.lang].opts["skip-words"]); + this.output.adjust = new CSL.Output.Queue.adjust(this.getOpt('punctuation-in-quote')); + this.registry = new CSL.Registry(this); + + // XXX For modular jurisdiction support, parameterize buildTokenLists(). + // XXX Feed as arguments: + // XXX * actual node to be walked (cslXml) + // XXX * actual target array + this.macros = {}; + this.build.area = "citation"; var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area); this.buildTokenLists(area_nodes, this[this.build.area].tokens); + this.build.area = "bibliography"; var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area); this.buildTokenLists(area_nodes, this[this.build.area].tokens); + this.build.area = "intext"; var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area); this.buildTokenLists(area_nodes, this[this.build.area].tokens); + this.juris = {}; + this.configureTokenLists(); + this.disambiguate = new CSL.Disambiguation(this); + this.splice_delimiter = false; + + // + // date parser + // this.fun.dateparser = CSL.DateParser; + // + // flip-flopper for inline markup + // this.fun.flipflopper = new CSL.Util.FlipFlopper(this); + // + // utility functions for quotes + // this.setCloseQuotesArray(); + // + // configure ordinal numbers generator + // this.fun.ordinalizer.init(this); + // + // configure long ordinal numbers generator + // this.fun.long_ordinalizer.init(this); + // + // set up page mangler + // this.fun.page_mangler = CSL.Util.PageRangeMangler.getFunction(this, "page"); this.fun.year_mangler = CSL.Util.PageRangeMangler.getFunction(this, "year"); + this.setOutputFormat("html"); }; + CSL.Engine.prototype.setCloseQuotesArray = function () { var ret; ret = []; @@ -2749,6 +3626,8 @@ CSL.Engine.prototype.setCloseQuotesArray = function () { ret.push("'"); this.opt.close_quotes_array = ret; }; + +// Walker for preparsed XML input CSL.makeBuilder = function (me, target) { var var_stack = []; var node_stack = []; @@ -2793,6 +3672,8 @@ CSL.makeBuilder = function (me, target) { } return buildStyle; }; + + CSL.Engine.prototype.buildTokenLists = function (area_nodes, target) { if (!this.cslXml.getNodeValue(area_nodes)) { return; @@ -2806,17 +3687,29 @@ CSL.Engine.prototype.buildTokenLists = function (area_nodes, target) { } builder(mynode); }; + + CSL.Engine.prototype.setStyleAttributes = function () { var dummy, attributes, attrname; + // Protect against DOM engines that deliver a top-level document + // (needed for createElement) that does not contain our top-level node. + // + // The string coercion on this.cslXml.tagName addresses a bizarre + // condition on the top-level node in jsdom running under node.js, in which: + // (1) typeof this.cslXml.tagName === "undefined"; and + // (2) !this.cslXml.tagName === false + // Coerced, it becomes an empty string. var dummy = {}; dummy.name = this.cslXml.nodename(this.cslXml.dataObj); attributes = this.cslXml.attributes(this.cslXml.dataObj); for (attrname in attributes) { if (attributes.hasOwnProperty(attrname)) { + // attr = attributes[key]; CSL.Attributes[attrname].call(dummy, this, attributes[attrname]); } } }; + CSL.Engine.prototype.getTerm = function (term, form, plural, gender, mode, forceDefaultLocale) { if (term && term.match(/[A-Z]/) && term === term.toUpperCase()) { CSL.debug("Warning: term key is in uppercase form: "+term); @@ -2829,12 +3722,14 @@ CSL.Engine.prototype.getTerm = function (term, form, plural, gender, mode, force lang = this.opt.lang; } var ret = CSL.Engine.getField(CSL.LOOSE, this.locale[lang].terms, term, form, plural, gender); + // XXXXX Temporary, until locale term is deployed in CSL. if (!ret && term === "range-delimiter") { ret = "\u2013"; } + // XXXXX Not so good if mode is neither strict nor tolerant ... if (typeof ret === "undefined") { if (mode === CSL.STRICT) { - throw "Error in getTerm: term \"" + term + "\" does not exist."; + CSL.error("Error in getTerm: term \"" + term + "\" does not exist."); } else if (mode === CSL.TOLERANT) { ret = ""; } @@ -2844,6 +3739,7 @@ CSL.Engine.prototype.getTerm = function (term, form, plural, gender, mode, force } return ret; }; + CSL.Engine.prototype.getDate = function (form, forceDefaultLocale) { var lang; if (forceDefaultLocale) { @@ -2857,6 +3753,7 @@ CSL.Engine.prototype.getDate = function (form, forceDefaultLocale) { return false; } }; + CSL.Engine.prototype.getOpt = function (arg) { if ("undefined" !== typeof this.locale[this.opt.lang].opts[arg]) { return this.locale[this.opt.lang].opts[arg]; @@ -2864,9 +3761,13 @@ CSL.Engine.prototype.getOpt = function (arg) { return false; } }; + + + CSL.Engine.prototype.getVariable = function (Item, varname, form, plural) { return CSL.Engine.getField(CSL.LOOSE, Item, varname, form, plural); }; + CSL.Engine.prototype.getDateNum = function (ItemField, partname) { if ("undefined" === typeof ItemField) { return 0; @@ -2874,12 +3775,13 @@ CSL.Engine.prototype.getDateNum = function (ItemField, partname) { return ItemField[partname]; } }; + CSL.Engine.getField = function (mode, hash, term, form, plural, gender) { var ret, forms, f, pos, len, hashterm; ret = ""; if ("undefined" === typeof hash[term]) { if (mode === CSL.STRICT) { - throw "Error in getField: term \"" + term + "\" does not exist."; + CSL.error("Error in getField: term \"" + term + "\" does not exist."); } else { return undefined; } @@ -2918,10 +3820,13 @@ CSL.Engine.getField = function (mode, hash, term, form, plural, gender) { } return ret; }; + CSL.Engine.prototype.configureTokenLists = function () { var area, pos, len; + //for each (var area in ["citation", "citation_sort", "bibliography","bibliography_sort"]) { len = CSL.AREAS.length; for (pos = 0; pos < len; pos += 1) { + //var ret = []; area = CSL.AREAS[pos]; var tokens = this[area].tokens; this.configureTokenList(tokens); @@ -2929,12 +3834,15 @@ CSL.Engine.prototype.configureTokenLists = function () { this.version = CSL.version; return this.state; }; + CSL.Engine.prototype.configureTokenList = function (tokens) { var dateparts_master, token, dateparts, part, ppos, pppos, llen, lllen; dateparts_master = ["year", "month", "day"]; llen = tokens.length - 1; for (ppos = llen; ppos > -1; ppos += -1) { token = tokens[ppos]; + //token.pos = ppos; + //ret.push(token); if ("date" === token.name && CSL.END === token.tokentype) { dateparts = []; } @@ -2957,6 +3865,7 @@ CSL.Engine.prototype.configureTokenList = function (tokens) { } } }; + CSL.Engine.prototype.refetchItems = function (ids) { var ret = []; for (var i = 0, ilen = ids.length; i < ilen; i += 1) { @@ -2964,16 +3873,24 @@ CSL.Engine.prototype.refetchItems = function (ids) { } return ret; }; + CSL.ITERATION = 0; + +// Wrapper for sys.retrieveItem supplied by calling application. +// Adds experimental fields embedded in the note field for +// style development trial and testing purposes. CSL.Engine.prototype.retrieveItem = function (id) { var Item, m, i; + if (!this.tmp.loadedItemIDs[id]) { this.tmp.loadedItemIDs[id] = true; } else { return this.registry.refhash[id]; } + if (this.opt.development_extensions.normalize_lang_keys_to_lowercase && "boolean" === typeof this.opt.development_extensions.normalize_lang_keys_to_lowercase) { + // This is a hack. Should properly be configured by a processor method after build. for (var i=0,ilen=this.opt["default-locale"].length; i<]/)) { + // Attempt to split field in two var m = Item.language.match(/(.*?)([<>])(.*)/); if (m[2] === "<") { Item["language-name"] = m[1]; @@ -3046,6 +3971,7 @@ CSL.Engine.prototype.retrieveItem = function (id) { } } } + if (Item.page) { Item["page-first"] = Item.page; var num = "" + Item.page; @@ -3054,13 +3980,17 @@ CSL.Engine.prototype.retrieveItem = function (id) { Item["page-first"] = m[0]; } } + // Optional development extensions if (this.opt.development_extensions.field_hack && Item.note) { + // false is for validFieldsForType (all conforming entries scrubbed when false) CSL.parseNoteFieldHacks(Item, false, this.opt.development_extensions.allow_field_hack_date_override); } + // not including locator-date for (var key in Item) { if (CSL.DATE_VARIABLES.indexOf(key.replace(/^alt-/, "")) > -1) { var dateobj = Item[key]; if (dateobj) { + // raw date parsing is harmless, but can be disabled if desired if (this.opt.development_extensions.raw_date_parsing) { if (dateobj.raw && (!dateobj["date-parts"] || dateobj["date-parts"].length === 0)) { dateobj = this.fun.dateparser.parseDateToObject(dateobj.raw); @@ -3072,6 +4002,7 @@ CSL.Engine.prototype.retrieveItem = function (id) { } if (this.opt.development_extensions.static_statute_locator) { if (Item.type && ["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) > -1) { + var varname; var elements = ["type", "title", "jurisdiction", "genre", "volume", "container-title"]; var legislation_id = []; @@ -3093,6 +4024,7 @@ CSL.Engine.prototype.retrieveItem = function (id) { Item.legislation_id = legislation_id.join("::"); } } + // For authority to name shape in legal styles if (this.opt.development_extensions.force_jurisdiction) { if ("string" === typeof Item.authority) { Item.authority = [ @@ -3113,11 +4045,14 @@ CSL.Engine.prototype.retrieveItem = function (id) { } } } + // Add getAbbreviation() call for title-short and container-title-short if (!Item["title-short"]) { Item["title-short"] = Item.shortTitle; } + // Add support for main_title_from_short_title if (this.opt.development_extensions.main_title_from_short_title) { - CSL.extractTitleAndSubtitle(Item); + var narrowSpaceLocale = this.opt["default-locale"][0].slice(0, 2).toLowerCase() === "fr"; + CSL.extractTitleAndSubtitle.call(this, Item, narrowSpaceLocale); } var isLegalType = ["bill","legal_case","legislation","gazette","regulation"].indexOf(Item.type) > -1; if (this.opt.development_extensions.force_jurisdiction && isLegalType) { @@ -3175,12 +4110,16 @@ CSL.Engine.prototype.retrieveItem = function (id) { } return this.registry.refhash[id]; }; + CSL.Engine.prototype.refetchItem = function (id) { return this.registry.refhash[id]; }; + CSL.Engine.prototype.refetchItem = function (id) { return this.registry.refhash[id]; } + +// Executed during style build CSL.Engine.prototype.setOpt = function (token, name, value) { if (token.name === "style" || token.name === "cslstyle") { this.opt.inheritedAttributes[name] = value; @@ -3192,6 +4131,8 @@ CSL.Engine.prototype.setOpt = function (token, name, value) { token.strings[name] = value; } }; + +// Executed at runtime, since macros can occur in the context of citation or bibliography CSL.Engine.prototype.inheritOpt = function (token, attrname, parentname, defaultValue) { if ("undefined" !== typeof token.strings[attrname]) { return token.strings[attrname]; @@ -3204,11 +4145,24 @@ CSL.Engine.prototype.inheritOpt = function (token, attrname, parentname, default } } }; + CSL.Engine.prototype.remapSectionVariable = function (inputList) { + // We have items with a value in the section field (on Item) that must + // be mapped to the locator field (on item). We simply prepend it as + // a string here, and handle all parsing of the resulting string + // in processNumber(). Plurals and numeric are set in processNumber(). + + // Because the target is in the citation item (lowercase), the + // remapping cannot take place when the Item data is received. + // Citation provides a list of Item/item pairs, hence the iteration + // used here. for (var i = 0, ilen = inputList.length; i < ilen; i += 1) { var Item = inputList[i][0]; var item = inputList[i][1]; + if (["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) > -1) { + // If a locator value exists, then + // (a) Leave be an overriding label at the start of the locator field, defaulting to label value if (item.locator) { item.locator = item.locator.trim(); var m = item.locator.match(CSL.STATUTE_SUBDIV_PLAIN_REGEX_FRONT); @@ -3220,6 +4174,8 @@ CSL.Engine.prototype.remapSectionVariable = function (inputList) { } } } + // If a section value exists, then + // (a) Apply an overriding label at the start of the section field, defaulting to sec. var sectionMasterLabel = null; if (Item.section) { Item.section = Item.section.trim(); @@ -3231,10 +4187,17 @@ CSL.Engine.prototype.remapSectionVariable = function (inputList) { sectionMasterLabel = m[0].trim(); } } + // If section is nil, then + // (a) Do nothing if (Item.section) { + // If section exists and locator is nil + // (a) Set section string in locator field if (!item.locator) { item.locator = Item.section; } else { + // If both section and locator exist, then + // (a) If locator starts with p., remove p., merge with space or no-space, and set in locator field + // (b) If locator starts with non-p., prepend section value to locator with space, and set in locator field var m = item.locator.match(/^([^ ]*)\s*(.*)/); var space = " "; if (m) { @@ -3249,32 +4212,43 @@ CSL.Engine.prototype.remapSectionVariable = function (inputList) { } item.locator = Item.section + space + item.locator; } + //Item.section = ""; } item.label = ""; + // And that's it. Pre-parse complete. } } }; + + CSL.Engine.prototype.setNumberLabels = function (Item) { if (Item.number && ["bill", "gazette", "legislation","regulation","treaty"].indexOf(Item.type) > -1 && this.opt.development_extensions.static_statute_locator && !this.tmp.shadow_numbers["number"]) { + this.tmp.shadow_numbers["number"] = {}; this.tmp.shadow_numbers["number"].values = []; this.tmp.shadow_numbers["number"].plural = 0; this.tmp.shadow_numbers["number"].numeric = false; this.tmp.shadow_numbers["number"].label = false; + + // Labels embedded in number variable var value = "" + Item.number; value = value.split("\\").join(""); + // Get first word, parse out labels only if it parses var firstword = value.split(/\s+/)[0]; var firstlabel = CSL.STATUTE_SUBDIV_STRINGS[firstword]; if (firstlabel) { + // Get list and match var splt = value.split(CSL.STATUTE_SUBDIV_PLAIN_REGEX); if (splt.length > 1) { + // Convert matches to localized form var lst = []; for (var j=1, jlen=splt.length; j < jlen; j += 1) { lst.push(splt[j].replace(/\s*$/, "").replace(/^\s*/, "")); } + // Preemptively save to shadow_numbers value = lst.join(" "); } else { value = splt[0]; @@ -3288,15 +4262,32 @@ CSL.Engine.prototype.setNumberLabels = function (Item) { } } }; + +/*global CSL: true */ + CSL.substituteOne = function (template) { return function (state, list) { if (!list) { return ""; } else { + // ("string" === typeof list) return template.replace("%%STRING%%", list); } }; }; + + +/** + * Two-tiered substitutions gadget. + *

This 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;j 0; pos += -1) { if (blobs[pos].checkLast) { @@ -4136,11 +5328,17 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent } if ("string" === typeof blob) { ret += txt_esc(use_delim); + // XXX Blob should be run through flipflop and flattened here. + // (I think it must be a fragment of text around a numeric + // variable) ret += blob; if (state.tmp.count_offset_characters) { + //state.tmp.offset_characters += (use_delim.length + blob.length); state.tmp.offset_characters += (use_delim.length); } } else if (in_cite) { + // pass + // Okay, so this does it -- but we're now not able to return a string! if (ret) { ret = [ret, blob]; } else { @@ -4152,7 +5350,9 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent } else { str = blob.formatter.format(blob.num, blob.gender); } + // Workaround to get a more or less accurate value. var strlen = str.replace(/<[^>]*>/g, "").length; + // notSerious this.append(str, "empty", true); var str_blob = this.pop(); var count_offset_characters = state.tmp.count_offset_characters; @@ -4177,16 +5377,23 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent str = txt_esc(blob.strings.prefix) + str + txt_esc(blob.strings.suffix); var addme = ""; if (blob.status === CSL.END) { + //print(" CSL.END"); addme = txt_esc(blob.range_prefix); } else if (blob.status === CSL.SUCCESSOR) { + //print(" CSL.SUCCESSOR"); addme = txt_esc(blob.successor_prefix); } else if (blob.status === CSL.START) { + //print(" CSL.START"); if (pos > 0 && !blob.suppress_splice_prefix) { addme = txt_esc(blob.splice_prefix); } else { addme = ""; } } else if (blob.status === CSL.SEEN) { + //print(" CSL.SEEN"); + + // THIS IS NOT THE PROPER FUNCTION OF CSL.SEEN, IS IT? + addme = txt_esc(blob.splice_prefix); } ret += addme; @@ -4198,10 +5405,13 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent } return ret; }; + CSL.Output.Queue.purgeEmptyBlobs = function (parent) { + //print("START1"); if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) { return; } + // back-to-front, bottom-first for (var i=parent.blobs.length-1;i>-1;i--) { CSL.Output.Queue.purgeEmptyBlobs(parent.blobs[i]); var child = parent.blobs[i]; @@ -4216,17 +5426,27 @@ CSL.Output.Queue.purgeEmptyBlobs = function (parent) { } } } + //print(" end"); }; + +// Adjustments to be made: +// +// * Never migrate beyond a @quotes node +// * Never migrate into a num node. + CSL.Output.Queue.adjust = function (punctInQuote) { + var NO_SWAP_IN = { ";": true, ":": true }; + var NO_SWAP_OUT = { ".": true, "!": true, "?": true }; + var LtoR_MAP = { "!": { ".": "!", @@ -4271,6 +5491,7 @@ CSL.Output.Queue.adjust = function (punctInQuote) { ".": ";" } }; + var SWAP_IN = {}; var SWAP_OUT = {}; var PUNCT = {}; @@ -4287,6 +5508,7 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } PUNCT_OR_SPACE[" "] = true; PUNCT_OR_SPACE[" "] = true; + var RtoL_MAP = {}; for (var key in LtoR_MAP) { for (var subkey in LtoR_MAP[key]) { @@ -4296,9 +5518,11 @@ CSL.Output.Queue.adjust = function (punctInQuote) { RtoL_MAP[subkey][key] = LtoR_MAP[key][subkey]; } } + function blobIsNumber(blob) { return ("number" === typeof blob.num || (blob.blobs && blob.blobs.length === 1 && "number" === typeof blob.blobs[0].num)); } + function blobEndsInNumber(blob) { if ("number" === typeof blob.num) { return true; @@ -4310,6 +5534,7 @@ CSL.Output.Queue.adjust = function (punctInQuote) { return true; } } + function blobHasDecorations(blob,includeQuotes) { var ret = false; var decorlist = ['@font-style','@font-variant','@font-weight','@text-decoration','@vertical-align']; @@ -4326,6 +5551,7 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } return ret; } + function blobHasDescendantQuotes(blob) { if (blob.decorations) { for (var i=0,ilen=blob.decorations.length;i-1;i--) { this.upward(parent.blobs[i]); var parentStrings = parent.strings; var childStrings = parent.blobs[i].strings; if (i === 0) { + // Remove leading space on first-position child node prefix if there is a trailing space on the node prefix above if (" " === parentStrings.prefix.slice(-1) && " " === childStrings.prefix.slice(0, 1)) { childStrings.prefix = childStrings.prefix.slice(1); } + // Migrate leading punctuation or space on a first-position prefix upward var childChar = childStrings.prefix.slice(0, 1); if (!parentDecorations && PUNCT_OR_SPACE[childChar] && !parentStrings.prefix) { parentStrings.prefix += childChar; @@ -4465,7 +5708,9 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } } if (i === (parent.blobs.length - 1)) { + // Migrate trailing space ONLY on a last-position suffix upward, controlling for duplicates var childChar = childStrings.suffix.slice(-1); + // ZZZ Loosened to fix initialized names wrapped in a span and followed by a period if (!parentDecorations && [" "].indexOf(childChar) > -1) { if (parentStrings.suffix.slice(0,1) !== childChar) { parentStrings.suffix = childChar + parentStrings.suffix; @@ -4474,31 +5719,53 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } } if (parentStrings.delimiter && i > 0) { + // Remove leading space on mid-position child node prefix if there is a trailing space on delimiter above if (PUNCT_OR_SPACE[parentStrings.delimiter.slice(-1)] && parentStrings.delimiter.slice(-1) === childStrings.prefix.slice(0, 1)) { + childStrings.prefix = childStrings.prefix.slice(1); } } + // Siblings are handled in adjustNearsideSuffixes() } + //print(" end"); } + function leftward (parent) { + // Terminus if no blobs if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) { return; } + for (var i=parent.blobs.length-1;i>-1;i--) { this.leftward(parent.blobs[i]); + // This is a delicate one. + // + // Migrate if: + // * there is no umbrella delimiter [ok] + // * neither the child nor its sibling is a number [ok] + // * decorations exist neither on the child nor on the sibling [ok] + // * sibling prefix char is a swapping char [ok] + // + // Suppress without migration if: + // * sibling prefix char matches child suffix char or + // * child suffix is empty and sibling prefix char match last field char if ((i < parent.blobs.length -1) && !parent.strings.delimiter) { + // If there is a trailing swappable character on a sibling prefix with no intervening delimiter, copy it to suffix, + // controlling for duplicates var child = parent.blobs[i]; var childChar = child.strings.suffix.slice(-1); var sibling = parent.blobs[i+1]; var siblingChar = sibling.strings.prefix.slice(0, 1); var hasDecorations = blobHasDecorations(child) || blobHasDecorations(sibling); var hasNumber = "number" === typeof childChar || "number" === typeof siblingChar; + if (!hasDecorations && !hasNumber && PUNCT[siblingChar] && !hasNumber) { var suffixAndPrefixMatch = siblingChar === child.strings.suffix.slice(-1); var suffixAndFieldMatch = (!child.strings.suffix && "string" === typeof child.blobs && child.blobs.slice(-1) === siblingChar); if (!suffixAndPrefixMatch && !suffixAndFieldMatch) { mergeChars(child, 'suffix', sibling, 'prefix'); + //child.strings.suffix += siblingChar; } else { sibling.strings.prefix = sibling.strings.prefix.slice(1); } @@ -4506,17 +5773,26 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } } } + function downward (parent) { + //print("START3"); + // Terminus if no blobs if (parent.blobs && "string" == typeof parent.blobs) { if (PUNCT[parent.strings.suffix.slice(0,1)] && parent.strings.suffix.slice(0,1) === parent.blobs.slice(-1)) { + parent.strings.suffix = parent.strings.suffix.slice(1); } return; } else if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) { return; } + //if (top) { + // print("JSON "+JSON.stringify(parent, ["strings", "decorations", "blobs", "prefix", "suffix", "delimiter"], 2)); + //} + var parentStrings = parent.strings; + // Check for numeric child var someChildrenAreNumbers = false; for (var i=0,ilen=parent.blobs.length;i-1;i--) { @@ -4536,14 +5813,30 @@ CSL.Output.Queue.adjust = function (punctInQuote) { parentStrings.delimiter = parentStrings.delimiter.slice(1); } } + // back-to-front, top-first for (var i=parent.blobs.length-1;i>-1;i--) { var child = parent.blobs[i]; var childStrings = parent.blobs[i].strings; var childDecorations = blobHasDecorations(child, true); var childIsNumber = blobIsNumber(child); + if (i === (parent.blobs.length - 1)) { + + //if (blobHasDescendantQuotes(child)) { + // print("JSON "+JSON.stringify(parent, ["strings", "decorations", "blobs", "prefix", "suffix", "delimiter"])); + //} + if (true || !someChildrenAreNumbers) { + // If we have decorations, drill down to see if there are quotes below. + // If so, we allow migration anyway. + // Original discussion is here: + // https://forums.zotero.org/discussion/37091/citeproc-bug-punctuation-in-quotes/ var parentChar = parentStrings.suffix.slice(0, 1); + + // Hmm. + // Consider writing out the matching child from blobHasDescendant functions. + // It should save some cycles, and produce the same result. + var allowMigration = false; if (PUNCT[parentChar]) { allowMigration = blobHasDescendantMergingPunctuation(parentChar,child); @@ -4569,39 +5862,68 @@ CSL.Output.Queue.adjust = function (punctInQuote) { if (childStrings.suffix.slice(-1) === " " && parentStrings.suffix.slice(0,1) === " ") { parentStrings.suffix = parentStrings.suffix.slice(1); } + // More duplicates control if (PUNCT_OR_SPACE[childStrings.suffix.slice(0,1)]) { if ("string" === typeof child.blobs && child.blobs.slice(-1) === childStrings.suffix.slice(0,1)) { + // Remove parent punctuation of it duplicates the last character of a field childStrings.suffix = childStrings.suffix.slice(1); } if (childStrings.suffix.slice(-1) === parentStrings.suffix.slice(0, 1)) { + // Remove duplicate punctuation on child suffix parentStrings.suffix = parentStrings.suffix.slice(0, -1); } } } + // Squash dupes if (matchLastChar(parent,parent.strings.suffix.slice(0,1))) { parent.strings.suffix = parent.strings.suffix.slice(1); } } else if (parentStrings.delimiter) { + // Remove trailing space on mid-position child node suffix if there is a leading space on delimiter above if (PUNCT_OR_SPACE[parentStrings.delimiter.slice(0,1)] && parentStrings.delimiter.slice(0, 1) === childStrings.suffix.slice(-1)) { + parent.blobs[i].strings.suffix = parent.blobs[i].strings.suffix.slice(0, -1); + } } else { + // Otherwise it's a sibling. We don't care about moving spaces here, just suppress a duplicate var siblingStrings = parent.blobs[i+1].strings; if (!blobIsNumber(child) && !childDecorations && PUNCT_OR_SPACE[childStrings.suffix.slice(-1)] && childStrings.suffix.slice(-1) === siblingStrings.prefix.slice(0, 1)) { + siblingStrings.prefix = siblingStrings.prefix.slice(1); } } + // If field content ends with swappable punctuation, suppress swappable punctuation in style suffix. if (!childIsNumber && !childDecorations && PUNCT[childStrings.suffix.slice(0,1)] && "string" === typeof child.blobs) { + mergeChars(child, 'blobs', child, 'suffix'); } this.downward(parent.blobs[i]); } +/* + if (top) { + + var seen = []; + print(JSON.stringify(parent, function(key, val) { + if (!val || key === 'alldecor') return; + if (typeof val == "object") { + if (seen.indexOf(val) >= 0) + return + seen.push(val) + } + return val + },2)); + } +*/ + + //print(" end"); } + // Abstract out a couple of utility functions, used in fix() below. function swapToTheLeft (child) { var childChar = child.strings.suffix.slice(0,1); if ("string" === typeof child.blobs) { @@ -4631,11 +5953,21 @@ CSL.Output.Queue.adjust = function (punctInQuote) { } } } + function fix (parent) { + // Terminus if no blobs if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) { return; } + + //print("START4"); + // Do the swap, front-to-back, bottom-first var lastChar; + + // XXX Two things to fix with this: + // XXX (1) Stalls after one character + // XXX (2) Moves colon and semicolon, both of which SHOULD stall + for (var i=0,ilen=parent.blobs.length;i this.opt = { inheritedAttributes: {} }; + this.tokens = []; + // Placeholder function this.srt = new CSL.Registry.Comparifier(state, "citation_sort"); + // + // configuration array to hold the collapse + // options, if any. this.opt.collapse = []; + // + // disambiguate options this.opt["disambiguate-add-names"] = false; this.opt["disambiguate-add-givenname"] = false; this.opt["disambiguate-add-year-suffix"] = false; this.opt["givenname-disambiguation-rule"] = "by-cite"; this.opt["near-note-distance"] = 5; + this.opt.topdecor = []; this.opt.layout_decorations = []; this.opt.layout_prefix = ""; this.opt.layout_suffix = ""; this.opt.layout_delimiter = ""; + // + // sorting this.opt.sort_locales = []; this.opt.max_number_of_names = 0; this.root = "citation"; }; + + CSL.Engine.Bibliography = function () { this.opt = { inheritedAttributes: {} }; this.tokens = []; + this.opt.collapse = []; + this.opt.topdecor = []; this.opt.layout_decorations = []; this.opt.layout_prefix = ""; @@ -4949,21 +6504,30 @@ CSL.Engine.Bibliography = function () { this.opt.layout_delimiter = ""; this.opt["line-spacing"] = 1; this.opt["entry-spacing"] = 1; + // + // sorting this.opt.sort_locales = []; this.opt.max_number_of_names = 0; this.root = "bibliography"; }; + + CSL.Engine.BibliographySort = function () { this.tokens = []; this.opt = {}; this.opt.sort_directions = []; this.opt.topdecor = []; + // Holds the final citation-number sort direction, for use + // in applying numbers in cs:citation and cs:bibliography. + // Value is exclusively controlled by cs:key in bibliography_sort this.opt.citation_number_sort_direction = CSL.ASCENDING; this.opt.citation_number_secondary = false; this.tmp = {}; this.keys = []; this.root = "bibliography"; }; + + CSL.Engine.CitationSort = function () { this.tokens = []; this.opt = {}; @@ -4972,36 +6536,61 @@ CSL.Engine.CitationSort = function () { this.opt.topdecor = []; this.root = "citation"; }; + CSL.Engine.InText = function () { + // InText options area. + // Holds a mixture of persistent and ephemeral + // options and scratch data used during processing of + // a citation.

this.opt = { inheritedAttributes: {} }; + this.tokens = []; + // Placeholder function + //this.srt = new CSL.Registry.Comparifier(state, "citation_sort"); + // + // configuration array to hold the collapse + // options, if any. this.opt.collapse = []; + // + // disambiguate options this.opt["disambiguate-add-names"] = false; this.opt["disambiguate-add-givenname"] = false; this.opt["disambiguate-add-year-suffix"] = false; this.opt["givenname-disambiguation-rule"] = "by-cite"; this.opt["near-note-distance"] = 5; + this.opt.topdecor = []; this.opt.layout_decorations = []; this.opt.layout_prefix = ""; this.opt.layout_suffix = ""; this.opt.layout_delimiter = ""; + // + // sorting this.opt.sort_locales = []; this.opt.max_number_of_names = 0; this.root = "intext"; }; + +/*global CSL: true */ + CSL.Engine.prototype.previewCitationCluster = function (citation, citationsPre, citationsPost, newMode) { + // Generate output for a hypothetical citation at the current position, + // Leave the registry in the same state in which it was found. + //print("################### previewCitationCluster() #################"); var oldMode = this.opt.mode; this.setOutputFormat(newMode); + // Avoids generating unwanted ibids, if the citationID already exists in document if (citation.citationID) { delete citation.citationID; } var ret = this.processCitationCluster(citation, citationsPre, citationsPost, CSL.PREVIEW); + this.setOutputFormat(oldMode); return ret[1]; }; + CSL.Engine.prototype.appendCitationCluster = function (citation) { var citationsPre = []; var len = this.registry.citationreg.citationByIndex.length; @@ -5009,22 +6598,54 @@ CSL.Engine.prototype.appendCitationCluster = function (citation) { var c = this.registry.citationreg.citationByIndex[pos]; citationsPre.push(["" + c.citationID, c.properties.noteIndex]); } + // Drop the data segment to return a list of pos/string pairs. return this.processCitationCluster(citation, citationsPre, [])[1]; }; + + CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, citationsPost, flag) { var c, preCitation, postCitation, i, ilen, j, jlen, k, klen, n, nlen, key, Item, item, noteCitations, textCitations, m, citationsInNote; this.debug = false; this.tmp.loadedItemIDs = {}; + + // Revert citation dereference from 2ffc4664ae + //citation = JSON.parse(JSON.stringify(citation)); + + //print("################### processCitationCluster() #################"); this.tmp.citation_errors = []; this.registry.return_data = {"bibchange": false}; + + // make sure this citation has a unique ID, and register it in citationById. this.setCitationId(citation); + var oldCitationList; var oldItemList; var oldAmbigs; if (flag === CSL.PREVIEW) { + //SNIP-START + if (this.debug) { + CSL.debug("****** start state save *********"); + } + //SNIP-END + // + // Simplify. + + // Take a slice of existing citations. oldCitationList = this.registry.citationreg.citationByIndex.slice(); + + // Take a slice of current items, for later use with update. oldItemList = this.registry.reflist.slice(); + + // Make a list of preview citation ref objects. Omit the current + // citation, because it will not exist in registry if: (a) this is + // a new citation; or (b) the calling application is assigning + // new citationIDs for every transaction. var newCitationList = citationsPre.concat(citationsPost); + + // Make a full list of desired ids, for use in preview update, + // and a hash list of same while we're at it. + // First step through known citations, then step through + // the items in the citation for preview. var newItemIds = {}; var newItemIdsList = []; for (var i = 0, ilen = newCitationList.length; i < ilen; i += 1) { @@ -5038,6 +6659,8 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, newItemIds[citation.citationItems[j].id] = true; newItemIdsList.push("" + citation.citationItems[j].id); } + + // Clone and save off disambigs of items that will be lost. oldAmbigs = {}; for (var i = 0, ilen = oldItemList.length; i < ilen; i += 1) { if (!newItemIds[oldItemList[i].id]) { @@ -5050,11 +6673,34 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } } } + + // Update items. This will produce the base name data and sort things. + // Possibly unnecessary? + //this.updateItems(this.registry.mylist.concat(tmpItems)); + + //SNIP-START + if (this.debug) { + CSL.debug("****** end state save *********"); + } + //SNIP-END } + this.tmp.taintedCitationIDs = {}; var sortedItems = []; + + // Styles that use note backreferencing with a by-cite + // givenname disambiguation rule include the note number + // in the cite for disambiguation purposes. Correct resolution + // of disambiguate="true" conditions on first-reference cites + // in certain editing scenarios (e.g. where a cite is moved across + // notes) requires that disambiguation be rerun on cites + // affected by the edit. var rerunAkeys = {}; + + // retrieve item data and compose items for use in rendering + // attach pointer to item data to shared copy for good measure for (var i = 0, ilen = citation.citationItems.length; i < ilen; i += 1) { + // Protect against caller-side overwrites to locator strings etc item = {}; for (var key in citation.citationItems[i]) { item[key] = citation.citationItems[i][key]; @@ -5083,7 +6729,13 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, sortedItems.push(newitem); citation.citationItems[i].item = Item; } + + // ZZZ sort stuff moved from here. + + // attach the sorted list to the citation item citation.sortedItems = sortedItems; + + // build reconstituted citations list in current document order var citationByIndex = []; var citationById = {}; var lastNotePos; @@ -5091,11 +6743,11 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, preCitation = citationsPre[i]; if (this.opt.development_extensions.strict_inputs) { if (citationById[preCitation[0]]) { - throw "Previously referenced citationID " + preCitation[0] + " encountered in citationsPre"; + CSL.error("Previously referenced citationID " + preCitation[0] + " encountered in citationsPre"); } if (preCitation[1]) { if (lastNotePos > preCitation[1]) { - throw "Note index sequence is not sane at citationsPre[" + i + "]"; + CSL.debug("Note index sequence is not sane at citationsPre[" + i + "]"); } lastNotePos = preCitation[1]; } @@ -5111,11 +6763,11 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } if (this.opt.development_extensions.strict_inputs) { if (citationById[citation.citationID]) { - throw "Citation with previously referenced citationID " + citation.citationID; + CSL.error("Citation with previously referenced citationID " + citation.citationID); } if (citation.properties.noteIndex) { if (lastNotePos > citation.properties.noteIndex) { - throw "Note index sequence is not sane for citation " + citation.citationID; + CSL.debug("Note index sequence is not sane for citation " + citation.citationID); } lastNotePos = citation.properties.noteIndex; } @@ -5126,11 +6778,11 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, postCitation = citationsPost[i]; if (this.opt.development_extensions.strict_inputs) { if (citationById[postCitation[0]]) { - throw "Previously referenced citationID " + postCitation[0] + " encountered in citationsPost"; + CSL.error("Previously referenced citationID " + postCitation[0] + " encountered in citationsPost"); } if (postCitation[1]) { if (lastNotePos > postCitation[1]) { - throw "Note index sequence is not sane at postCitation[" + i + "]"; + CSL.debug("Note index sequence is not sane at postCitation[" + i + "]"); } lastNotePos = postCitation[1]; } @@ -5141,6 +6793,32 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } this.registry.citationreg.citationByIndex = citationByIndex; this.registry.citationreg.citationById = citationById; + + // + // The processor provides three facilities to support + // updates following position reevaluation. + // + // (1) The updateItems() function reports tainted ItemIDs + // to state.tmp.taintedItemIDs. + // + // (2) The processor memos the type of style referencing as + // CSL.NONE, CSL.NUMERIC or CSL.POSITION in state.opt.update_mode. + // + // XXXX: NO LONGER + // (3) For citations containing cites with backreference note numbers, + // a string image of the rendered citation is held in + // citation.properties.backref_citation, and a list of + // ItemIDs to be used to update the backreference note numbers + // is memoed at citation.properties.backref_index. When such + // citations change position, they can be updated with a + // series of simple find and replace operations, without + // need for rerendering. + // + + // + // Position evaluation! + // + // set positions in reconstituted list, noting taints this.registry.citationreg.citationsByItemId = {}; if (this.opt.update_mode === CSL.POSITION) { textCitations = []; @@ -5169,25 +6847,55 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } } } + // + // update bibliography items here + // if (flag !== CSL.ASSUME_ALL_ITEMS_REGISTERED) { + //SNIP-START + if (this.debug) { + CSL.debug("****** start update items *********"); + } + //SNIP-END + // true signals implicit updateItems (will not rerun sys.retrieveItem()) this.updateItems(update_items, null, null, true); + //SNIP-START + if (this.debug) { + CSL.debug("****** endo update items *********"); + } + //SNIP-END } + if (!this.opt.citation_number_sort && sortedItems && sortedItems.length > 1 && this.citation_sort.tokens.length > 0) { for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) { sortedItems[i][1].sortkeys = CSL.getSortKeys.call(this, sortedItems[i][0], "citation_sort"); } + + /* + * Grouped sort stuff (start) + */ + if (this.opt.grouped_sort && !citation.properties.unsorted) { + // Insert authorstring as key. for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) { var sortkeys = sortedItems[i][1].sortkeys; this.tmp.authorstring_request = true; + // Run getAmbiguousCite() with the current disambig + // parameters, and pick up authorstring from the registry. var mydisambig = this.registry.registry[sortedItems[i][0].id].disambig; + this.tmp.authorstring_request = true; CSL.getAmbiguousCite.call(this, sortedItems[i][0], mydisambig); var authorstring = this.registry.authorstrings[sortedItems[i][0].id]; this.tmp.authorstring_request = false; + sortedItems[i][1].sortkeys = [authorstring].concat(sortkeys); } + sortedItems.sort(this.citation.srt.compareCompositeKeys); + // Replace authorstring key in items with same (authorstring) with the + // keystring of first normal key. This forces grouped sorts, + // as discussed here: + // https://github.com/citation-style-language/schema/issues/40 var lastauthor = false; var thiskey = false; var thisauthor = false; @@ -5200,10 +6908,18 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, lastauthor = thisauthor; } } + /* + * Grouped sort stuff (end) + */ + if (!citation.properties.unsorted) { sortedItems.sort(this.citation.srt.compareCompositeKeys); } } + + // evaluate parallels + this.parallel.StartCitation(citation.sortedItems); + var citations; if (this.opt.update_mode === CSL.POSITION) { for (var i = 0; i < 2; i += 1) { @@ -5222,14 +6938,28 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, last_ref = {}; } for (k = 0, klen = onecitation.sortedItems.length; k < klen; k += 1) { - if (!this.registry.registry[onecitation.sortedItems[k][1].id].parallel) { - if (!citationsInNote[onecitation.properties.noteIndex]) { - citationsInNote[onecitation.properties.noteIndex] = 1; - } else { - citationsInNote[onecitation.properties.noteIndex] += 1; - } + if (onecitation.sortedItems[k][1].parallel && onecitation.sortedItems[k][1].parallel !== "first") { + continue; + } + if (!citationsInNote[onecitation.properties.noteIndex]) { + citationsInNote[onecitation.properties.noteIndex] = 1; + } else { + citationsInNote[onecitation.properties.noteIndex] += 1; } } + // Set the following: + // + // (1) position as required (as per current Zotero) + // (2) first-reference-note-number as required (on onecitation item) + // (3) near-note as required (on onecitation item, according to + // state.opt["near-note-distance"] parameter) + // (4) state.registry.citationreg.citationsByItemId. + // + // Any state changes caused by unsetting or resetting should + // trigger a single entry for the citations in + // state.tmp.taintedCitationIDs (can block on presence of + // state.registry.citationreg.citationsByItemId). + // for (k = 0, klen = citations[j].sortedItems.length; k < klen; k += 1) { item = citations[j].sortedItems[k]; var myid = item[0].id; @@ -5240,12 +6970,23 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } var incitationid; if (k > 0) { + // incitationid is only reached in the else branch + // following "undefined" === typeof first_ref[myid] + // below if (onecitation.sortedItems[k - 1][0].legislation_id) { incitationid = onecitation.sortedItems[k - 1][0].legislation_id; } else { incitationid = onecitation.sortedItems[k - 1][1].id; + //if (onecitation.sortedItems[k-1][1].parallel === "last") { + for (var l=k-2; l>-1; l--) { + if (onecitation.sortedItems[l][1].parallel === "first") { + incitationid = onecitation.sortedItems[l][1].id; + } + } + //} } } + // Don't touch item data of other cites when previewing if (flag === CSL.PREVIEW) { if (onecitation.citationID != citation.citationID) { if ("undefined" === typeof first_ref[item[1].id]) { @@ -5286,6 +7027,7 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } } var oldlastid; + if ("undefined" === typeof first_ref[myid] && onecitation.properties.mode !== "author-only") { first_ref[myid] = onecitation.properties.noteIndex; if (this.registry.registry[myid]) { @@ -5294,57 +7036,69 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, last_ref[myid] = onecitation.properties.noteIndex; item[1].position = CSL.POSITION_FIRST; } else { + // + // backward-looking position evaluation happens here. + // + // + // var ibidme = false; var suprame = false; + // XXX Ugly, but This is used in the second else-if branch condition below. if (j > 0) { - try { - var old_last_id_offset = 1; - if (citations[j-1].properties.mode === "author-only" && j > 1) { - old_last_id_offset = 2; - } - oldlastid = citations[j - old_last_id_offset].sortedItems.slice(-1)[0][1].id; - } catch (e) { - var err = "CSL Error\n"; - err += " " + e; - err += " in citation object " + citations[j - 1].citationID + " at index " + (j - 1); - throw err; + var old_last_id_offset = 1; + if (citations[j-1].properties.mode === "author-only" && j > 1) { + old_last_id_offset = 2; } + oldlastid = citations[j - old_last_id_offset].sortedItems.slice(-1)[0][1].id; if (citations[j - 1].sortedItems[0].slice(-1)[0].legislation_id) { oldlastid = citations[j - 1].sortedItems[0].slice(-1)[0].legislation_id; } } if (j > 0 && parseInt(k, 10) === 0 && citations[j - 1].properties.noteIndex !== citations[j].properties.noteIndex) { - var items = citations[(j - 1)].sortedItems; + // Case 1: source in previous onecitation + // (1) Threshold conditions + // (a) there must be a previous onecitation with one item + // (b) this item must be the first in this onecitation + // (c) the previous onecitation must contain a reference + // to the same item ... + // (d) the note numbers must be the same or consecutive. + // (this has some jiggery-pokery in it for parallels) var useme = false; + // XXX Can oldid be equated with oldlastid, I wonder ... var oldid = citations[j - 1].sortedItems[0][0].id; if (citations[j - 1].sortedItems[0][0].legislation_id) { oldid = citations[j - 1].sortedItems[0][0].legislation_id; } - if ((oldid == myid && citations[j - 1].properties.noteIndex >= (citations[j].properties.noteIndex - 1)) || citations[j - 1].sortedItems[0][1].id == this.registry.registry[item[1].id].parallel) { + if ((oldid == myid && citations[j - 1].properties.noteIndex >= (citations[j].properties.noteIndex - 1))) { if (citationsInNote[citations[j - 1].properties.noteIndex] === 1 || citations[j - 1].properties.noteIndex === 0) { useme = true; } } - for (n = 0, nlen = items.slice(1).length; n < nlen; n += 1) { - var itmp = items.slice(1)[n]; - if (!this.registry.registry[itmp[1].id].parallel || this.registry.registry[itmp[1].id].parallel == this.registry.registry[itmp[1].id]) { - useme = false; - } - } if (useme) { ibidme = true; } else { suprame = true; } } else if (k > 0 && incitationid == myid) { + // Case 2: immediately preceding source in this onecitation + // (1) Threshold conditions + // (a) there must be an imediately preceding reference to the + // same item in this onecitation; and ibidme = true; } else if (k === 0 && j > 0 && citations[j - 1].properties.noteIndex == citations[j].properties.noteIndex && citations[j - 1].sortedItems.length && oldlastid == myid) { + // ... in case there are separate citations in the same note ... + // Case 2 [take 2]: immediately preceding source in this onecitation + // (1) Threshold conditions + // (a) there must be an imediately preceding reference to the + // same item in this onecitation; and ibidme = true; } else { + // everything else is definitely subsequent suprame = true; } + // conditions var prev, prev_locator, prev_label, curr_locator, curr_label; if (ibidme) { if (k > 0) { @@ -5373,20 +7127,39 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, curr_locator = mylocator; } } + // triage if (ibidme && prev_locator && !curr_locator) { ibidme = false; suprame = true; + } if (ibidme) { if (!prev_locator && curr_locator) { + // (a) if the previous onecitation had no locator + // and this onecitation has one, use ibid+pages item[1].position = CSL.POSITION_IBID_WITH_LOCATOR; } else if (!prev_locator && !curr_locator) { + // (b) if the previous onecitation had no locator + // and this onecitation also has none, use ibid item[1].position = CSL.POSITION_IBID; + //print("setting ibid in cmd_cite()"); } else if (prev_locator && curr_locator === prev_locator) { + // (c) if the previous onecitation had a locator + // (page number, etc.) and this onecitation has + // a locator that is identical, use ibid + item[1].position = CSL.POSITION_IBID; + //print("setting ibid in cmd_cite() [2]"); } else if (prev_locator && curr_locator && curr_locator !== prev_locator) { + // (d) if the previous onecitation had a locator, + // and this onecitation has one that differs, + // use ibid+pages item[1].position = CSL.POSITION_IBID_WITH_LOCATOR; } else { + // (e) if the previous onecitation had a locator + // and this onecitation has none, use subsequent + // + // ... and everything else would be subsequent also ibidme = false; // just to be clear suprame = true; } @@ -5401,6 +7174,7 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, if (first_ref[myid] != onecitation.properties.noteIndex) { item[1]["first-reference-note-number"] = first_ref[myid]; if (this.registry.registry[myid]) { + // This is either the earliest recorded number, or the number of the current citation, whichever is smaller. var oldFirst = this.registry.citationreg.citationsByItemId[myid][0].properties.noteIndex; var newFirst = onecitation.properties.noteIndex; this.registry.registry[myid]['first-reference-note-number'] = newFirst < oldFirst ? newFirst: oldFirst; @@ -5449,6 +7223,8 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, for (var key in this.tmp.taintedItemIDs) { if (this.tmp.taintedItemIDs.hasOwnProperty(key)) { citations = this.registry.citationreg.citationsByItemId[key]; + // Current citation may be tainted but will not exist + // during previewing. if (citations) { for (var i = 0, ilen = citations.length; i < ilen; i += 1) { this.tmp.taintedCitationIDs[citations[i].citationID] = true; @@ -5456,32 +7232,78 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } } } + var ret = []; if (flag === CSL.PREVIEW) { + // If previewing, return only a rendered string + //SNIP-START + if (this.debug) { + CSL.debug("****** start run processor *********"); + } + //SNIP-END try { ret = this.process_CitationCluster.call(this, citation.sortedItems, citation); } catch (e) { CSL.error("Error running CSL processor for preview: "+e); } + + //SNIP-START + if (this.debug) { + CSL.debug("****** end run processor *********"); + CSL.debug("****** start state restore *********"); + } + //SNIP-END + // Wind out anything related to new items added for the preview. + // This means (1) names, (2) disambig state for affected items, + // (3) keys registered in the ambigs pool arrays, and (4) registry + // items. + // + + // restore sliced citations this.registry.citationreg.citationByIndex = oldCitationList; this.registry.citationreg.citationById = {}; for (var i = 0, ilen = oldCitationList.length; i < ilen; i += 1) { this.registry.citationreg.citationById[oldCitationList[i].citationID] = oldCitationList[i]; } + + //SNIP-START + if (this.debug) { + CSL.debug("****** start final update *********"); + } + //SNIP-END var oldItemIds = []; for (var i = 0, ilen = oldItemList.length; i < ilen; i += 1) { oldItemIds.push("" + oldItemList[i].id); } this.updateItems(oldItemIds, null, null, true); + //SNIP-START + if (this.debug) { + CSL.debug("****** end final update *********"); + } + //SNIP-END + // Roll back disambig states for (var key in oldAmbigs) { if (oldAmbigs.hasOwnProperty(key)) { this.registry.registry[key].disambig = oldAmbigs[key]; } } + //SNIP-START + if (this.debug) { + CSL.debug("****** end state restore *********"); + } + //SNIP-END } else { + // Rerun cites that have moved across citations or had a change + // in their number of subsequent references, so that disambiguate + // and subsequent-reference-count conditions are applied + // correctly in output. for (var rerunAkey in rerunAkeys) { this.disambiguate.run(rerunAkey, citation); } + // Run taints only if not previewing + // + // Push taints to the return object + // var obj; for (var key in this.tmp.taintedCitationIDs) { if (key == citation.citationID) { @@ -5494,6 +7316,7 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } mycitation.sortedItems.sort(this.citation.srt.compareCompositeKeys); } + // For error reporting this.tmp.citation_pos = mycitation.properties.index; this.tmp.citation_note_index = mycitation.properties.noteIndex; this.tmp.citation_id = "" + mycitation.citationID; @@ -5505,14 +7328,23 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, } this.tmp.taintedItemIDs = {}; this.tmp.taintedCitationIDs = {}; + + // For error reporting again this.tmp.citation_pos = citation.properties.index; this.tmp.citation_note_index = citation.properties.noteIndex; this.tmp.citation_id = "" + citation.citationID; + obj = []; obj.push(citationsPre.length); obj.push(this.process_CitationCluster.call(this, sortedItems, citation)); obj.push(citation.citationID); ret.push(obj); + // + // note for posterity: Rhino and Spidermonkey produce different + // sort results for items with matching keys. That discrepancy + // turned up a subtle bug in the parallel detection code, trapped + // at line 266, above, and in line 94 of util_parallel.js. + // ret.sort(function (a, b) { if (a[0] > b[0]) { return 1; @@ -5522,13 +7354,19 @@ CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, return 0; } }); + // + // In normal rendering, return is a list of two-part arrays, with the first element + // a citation index number, and the second the text to be inserted. + // } this.registry.return_data.citation_errors = this.tmp.citation_errors.slice(); return [this.registry.return_data, ret]; }; + CSL.Engine.prototype.process_CitationCluster = function (sortedItems, citation) { var str = ""; - this.parallel.StartCitation(sortedItems); + // Parallels must be evaluated in the calling function + //this.parallel.StartCitation(sortedItems); if (citation && citation.properties && citation.properties.mode === "composite") { citation.properties.mode = "author-only"; var firstChunk = CSL.getCitationCluster.call(this, sortedItems, citation); @@ -5537,6 +7375,7 @@ CSL.Engine.prototype.process_CitationCluster = function (sortedItems, citation) if (citation.properties.infix) { this.output.append(citation.properties.infix); secondChunk = this.output.string(this, this.output.queue); + // Had no idea this could return a single-element array! Go figure. if ("object" === typeof secondChunk) { secondChunk = secondChunk.join(""); } @@ -5555,6 +7394,7 @@ CSL.Engine.prototype.process_CitationCluster = function (sortedItems, citation) } return str; }; + CSL.Engine.prototype.makeCitationCluster = function (rawList) { var inputList, newitem, str, pos, len, item, Item; inputList = []; @@ -5565,6 +7405,7 @@ CSL.Engine.prototype.makeCitationCluster = function (rawList) { item[key] = rawList[pos][key]; } Item = this.retrieveItem("" + item.id); + // Code block is copied from processCitationCluster() above if (this.opt.development_extensions.locator_label_parse) { if (item.locator && ["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) === -1 && (!item.label || item.label === 'page')) { var m = CSL.LOCATOR_LABELS_REGEXP.exec(item.locator); @@ -5598,6 +7439,16 @@ CSL.Engine.prototype.makeCitationCluster = function (rawList) { var str = CSL.getCitationCluster.call(this, inputList); return str; }; + + +/** + * Get the undisambiguated version of a cite, without decorations + *

This 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;iSplice evaluation is done during cite + * rendering, and this method returns the + * result. Evaluation requires three items + * of information from the preceding cite, if + * one is present: the names used; the years + * used; and the suffix appended to the + * citation. These details are copied into + * the state object before processing begins, + * and are cleared by the processor on + * completion of the run.

+ */ + CSL.getSpliceDelimiter = function (last_locator, last_collapsed, pos) { + //print(pos + " after-collapse-delimiter="+this.citation.opt["after-collapse-delimiter"] + "\n cite_group_delimiter=" + this.tmp.use_cite_group_delimiter + "\n last_collapsed=" +last_collapsed + "\n have_collapsed=" +this.tmp.have_collapsed + "\n last_locator=" + last_locator) if (undefined !== this.citation.opt["after-collapse-delimiter"]) { if (last_locator) { this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"]; @@ -5676,16 +7550,52 @@ CSL.getSpliceDelimiter = function (last_locator, last_collapsed, pos) { if (this.tmp.have_collapsed && this.opt.xclass === "in-text" && this.opt.update_mode !== CSL.NUMERIC) { this.tmp.splice_delimiter = ", "; } else if (this.tmp.cite_locales[pos - 1]) { + // + // Must have a value to take effect. Use zero width space to force empty delimiter. var alt_affixes = this.tmp.cite_affixes[this.tmp.area][this.tmp.cite_locales[pos - 1]]; if (alt_affixes && alt_affixes.delimiter) { this.tmp.splice_delimiter = alt_affixes.delimiter; } } else if (!this.tmp.splice_delimiter) { + // This happens when no delimiter is set on cs:layout under cs:citation this.tmp.splice_delimiter = ""; } } + +/* + if (last_locator && "string" === typeof this.citation.opt["after-collapse-delimiter"]) { + this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"]; + } else if (last_collapsed && !this.tmp.have_collapsed && "string" === typeof this.citation.opt["after-collapse-delimiter"]) { + this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"]; + } else if (!last_collapsed && !this.tmp.have_collapsed && "string" === typeof this.citation.opt["after-collapse-delimiter"] && !this.citation.opt.collapse === "year-suffix") { + this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"]; + } else if (this.tmp.use_cite_group_delimiter) { + this.tmp.splice_delimiter = this.citation.opt.cite_group_delimiter; + } else if (this.tmp.have_collapsed && this.opt.xclass === "in-text" && this.opt.update_mode !== CSL.NUMERIC) { + this.tmp.splice_delimiter = ", "; + } else if (this.tmp.cite_locales[pos - 1]) { + // + // Must have a value to take effect. Use zero width space to force empty delimiter. + var alt_affixes = this.tmp.cite_affixes[this.tmp.area][this.tmp.cite_locales[pos - 1]]; + if (alt_affixes && alt_affixes.delimiter) { + this.tmp.splice_delimiter = alt_affixes.delimiter; + } + } else if (!this.tmp.splice_delimiter) { + // This happens when no delimiter is set on cs:layout under cs:citation + this.tmp.splice_delimiter = ""; + } +*/ + // Paranoia + //if (!this.tmp.splice_delimiter) { + // this.tmp.splice_delimiter = ""; + //} return this.tmp.splice_delimiter; }; + +/* + * Compose individual cites into a single string, with + * flexible inter-cite splicing. + */ CSL.getCitationCluster = function (inputList, citation) { var result, objects, myparams, len, pos, item, last_collapsed, params, empties, composite, compie, myblobs, Item, llen, ppos, obj, preceding_item, txt_esc, error_object, citationID, authorOnly, suppressAuthor; var citation_prefix = ""; @@ -5712,16 +7622,23 @@ CSL.getCitationCluster = function (inputList, citation) { this.tmp.last_years_used = []; this.tmp.backref_index = []; this.tmp.cite_locales = []; + var use_layout_prefix = this.output.checkNestedBrace.update(this.citation.opt.layout_prefix + citation_prefix); + //var use_layout_prefix = this.citation.opt.layout_prefix; + var suppressTrailingPunctuation = false; if (this.opt.xclass === "note" && this.citation.opt.suppressTrailingPunctuation) { suppressTrailingPunctuation = true; } if (citationID) { + //this.registry.citationreg.citationById[citationID].properties.backref_index = false; + //this.registry.citationreg.citationById[citationID].properties.backref_citation = false; if (this.registry.citationreg.citationById[citationID].properties["suppress-trailing-punctuation"]) { suppressTrailingPunctuation = true; } } + + // Adjust locator positions if that looks like a sensible thing to do. if (this.opt.xclass === "note") { var parasets = []; var lastTitle = false; @@ -5734,6 +7651,7 @@ CSL.getCitationCluster = function (inputList, citation) { var position = inputList[i][1].position; var id = inputList[i][0].id; if (title && type === "legal_case" && id !== lastID && position) { + // Start a fresh sublist if the item title does not match the last one if (title !== lastTitle || parasets.length === 0) { lst = []; parasets.push(lst); @@ -5744,11 +7662,13 @@ CSL.getCitationCluster = function (inputList, citation) { lastPosition = position; lastID = id; } + // We now have a list of sublists, each w/matching titles for (i=0, ilen=parasets.length; i < ilen; i += 1) { lst = parasets[i]; if (lst.length < 2) { continue; } + // Get the locator in last position, but only if it's the only one in the set. var locatorInLastPosition = lst.slice(-1)[0].locator; if (locatorInLastPosition) { for (var j=0, jlen=lst.length - 1; j < jlen; j += 1) { @@ -5757,6 +7677,7 @@ CSL.getCitationCluster = function (inputList, citation) { } } } + // move the locator here, if it's called for. if (locatorInLastPosition) { lst[0].locator = locatorInLastPosition; delete lst.slice(-1)[0].locator; @@ -5788,6 +7709,8 @@ CSL.getCitationCluster = function (inputList, citation) { last_locator = !!inputList[pos-1][1].locator; } params = {}; + + // Reset shadow_numbers here, suppress reset in getCite() this.tmp.shadow_numbers = {}; if (!this.tmp.just_looking && this.opt.hasPlaceholderTerm) { var output = this.output; @@ -5796,13 +7719,17 @@ CSL.getCitationCluster = function (inputList, citation) { CSL.getAmbiguousCite.call(this, Item, null, false, item); this.output = output; } + this.tmp.in_cite_predecessor = false; + // true is to block reset of shadow numbers if (pos > 0) { CSL.getCite.call(this, Item, item, "" + inputList[(pos - 1)][0].id, true); } else { this.tmp.term_predecessor = false; CSL.getCite.call(this, Item, item, null, true); } + + // Make a note of any errors if (!this.tmp.cite_renders_content) { error_object = { citationID: "" + this.tmp.citation_id, @@ -5814,17 +7741,19 @@ CSL.getCitationCluster = function (inputList, citation) { }; this.tmp.citation_errors.push(error_object); } - if (pos === (inputList.length - 1)) { - this.parallel.ComposeSet(); - } params.splice_delimiter = CSL.getSpliceDelimiter.call(this, last_locator, last_collapsed, pos); + // XXX This appears to be superfluous. if (item && item["author-only"]) { this.tmp.suppress_decorations = true; } + if (pos > 0) { preceding_item = inputList[pos - 1][1]; - var precedingEndsInPeriodOrComma = preceding_item.suffix && [".", ","].indexOf(preceding_item.suffix.slice(-1)) > -1; - var currentStartsWithPeriodOrComma = !preceding_item.suffix && item.prefix && [".", ","].indexOf(item.prefix.slice(0, 1)) > -1; + + // XXX OR if preceding suffix is empty, and the current prefix begins with a full stop. + + var precedingEndsInPeriodOrComma = preceding_item.suffix && [";", ".", ","].indexOf(preceding_item.suffix.slice(-1)) > -1; + var currentStartsWithPeriodOrComma = !preceding_item.suffix && item.prefix && [";", ".", ","].indexOf(item.prefix.slice(0, 1)) > -1; if (precedingEndsInPeriodOrComma || currentStartsWithPeriodOrComma) { var spaceidx = params.splice_delimiter.indexOf(" "); if (spaceidx > -1 && !currentStartsWithPeriodOrComma) { @@ -5836,28 +7765,44 @@ CSL.getCitationCluster = function (inputList, citation) { } params.suppress_decorations = this.tmp.suppress_decorations; params.have_collapsed = this.tmp.have_collapsed; + // + // XXXXX: capture parameters to an array, which + // will be of the same length as this.output.queue, + // corresponding to each element. + // myparams.push(params); if (item["author-only"]) { break; } } - this.tmp.has_purged_parallel = false; - this.parallel.PruneOutputQueue(this); + + this.parallel.purgeGroupsIfParallel(); + // + // output.queue is a simple array. do a slice + // of it to get each cite item, setting params from + // the array that was built in the preceding loop. + // empties = 0; myblobs = this.output.queue.slice(); + var citation_suffix = ""; if (citation) { citation_suffix = CSL.checkSuffixSpacePrepend(this, citation.properties.suffix); } var suffix = this.citation.opt.layout_suffix; var last_locale = this.tmp.cite_locales[this.tmp.cite_locales.length - 1]; + // + // Must have a value to take effect. Use zero width space to force empty suffix. if (last_locale && this.tmp.cite_affixes[this.tmp.area][last_locale] && this.tmp.cite_affixes[this.tmp.area][last_locale].suffix) { suffix = this.tmp.cite_affixes[this.tmp.area][last_locale].suffix; } if (CSL.TERMINAL_PUNCTUATION.slice(0, -1).indexOf(suffix.slice(0, 1)) > -1) { suffix = suffix.slice(0, 1); } + //print("=== FROM CITE ==="); suffix = this.output.checkNestedBrace.update(citation_suffix + suffix); + + for (var i=0,ilen=this.output.queue.length;i death by a thousand cuts) 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); } + // XXX This only applied to the "number" variable itself? Huh? + //this.setNumberLabels(Item); this.tmp.first_name_string = false; this.tmp.authority_stop_last = 0; }; + CSL.citeEnd = function (Item, item) { + // RESTORE PARAMETERS IF APPROPRIATE if (this.tmp.disambig_restore && this.registry.registry[Item.id]) { this.registry.registry[Item.id].disambig.names = this.tmp.disambig_restore.names.slice(); this.registry.registry[Item.id].disambig.givens = this.tmp.disambig_restore.givens.slice(); @@ -6102,7 +8096,9 @@ CSL.citeEnd = function (Item, item) { } } this.tmp.disambig_restore = false; + if (item && item.suffix) { + //this.tmp.last_suffix_used = this.tmp.suffix.value(); this.tmp.last_suffix_used = item.suffix; } else { this.tmp.last_suffix_used = ""; @@ -6110,24 +8106,38 @@ CSL.citeEnd = function (Item, item) { this.tmp.last_years_used = this.tmp.years_used.slice(); this.tmp.last_names_used = this.tmp.names_used.slice(); this.tmp.cut_var = false; + + // This is a hack, in a way; I have lost track of where + // the disambig (name rendering) settings used for rendering work their way + // into the registry. This resets defaults to the subsequent form, + // when first cites are rendered. + //if (this.tmp.disambig_restore && this.registry.registry[Item.id]) { + // this.registry.registry[Item.id].disambig = this.tmp.disambig_restore; + //} + //this.tmp.disambig_restore = false; this.tmp.disambig_request = false; + this.tmp.cite_locales.push(this.tmp.last_cite_locale); + if (this.tmp.issued_date && this.tmp.renders_collection_number) { var buf = []; for (var i = this.tmp.issued_date.list.length - 1; i > this.tmp.issued_date.pos; i += -1) { buf.push(this.tmp.issued_date.list.pop()); } + // Throw away the unwanted blob this.tmp.issued_date.list.pop(); + // Put the other stuff back for (i = buf.length - 1; i > -1; i += -1) { this.tmp.issued_date.list.push(buf.pop()); } - if (this.parallel.use_parallels) { - this.parallel.cite.issued = false; - } } this.tmp.issued_date = false; this.tmp.renders_collection_number = false; + }; + +/*global CSL: true */ + CSL.Engine.prototype.makeBibliography = function (bibsection) { var debug, ret, params, maxoffset, item, len, pos, tok, tokk, tokkk, entry_ids, entry_strings; debug = false; @@ -6153,6 +8163,7 @@ CSL.Engine.prototype.makeBibliography = function (bibsection) { } } } + // API change: added in version 1.0.51 if (!this.bibliography.tokens.length) { return false; } @@ -6160,10 +8171,36 @@ CSL.Engine.prototype.makeBibliography = function (bibsection) { this.opt.citation_number_slug = bibsection; bibsection = false; } + //SNIP-START + if (debug) { + len = this.bibliography.tokens.length; + for (pos = 0; pos < len; pos += 1) { + tok = this.bibliography.tokens[pos]; + CSL.debug("bibtok: " + tok.name); + } + CSL.debug("---"); + len = this.citation.tokens.length; + for (pos = 0; pos < len; pos += 1) { + tokk = this.citation.tokens[pos]; + CSL.debug("cittok: " + tok.name); + } + CSL.debug("---"); + len = this.bibliography_sort.tokens.length; + for (pos = 0; pos < len; pos += 1) { + tokkk = this.bibliography_sort.tokens[pos]; + CSL.debug("bibsorttok: " + tok.name); + } + } + //SNIP-END + + // For paged returns ret = CSL.getBibliographyEntries.call(this, bibsection); entry_ids = ret[0]; entry_strings = ret[1]; + + // For paged returns var done = ret[2]; + params = { "maxoffset": 0, "entryspacing": this.bibliography.opt["entry-spacing"], @@ -6189,9 +8226,14 @@ CSL.Engine.prototype.makeBibliography = function (bibsection) { } params.bibstart = this.fun.decorate.bibstart; params.bibend = this.fun.decorate.bibend; + this.opt.citation_number_slug = false; return [params, entry_strings]; }; + +/* + * Compose individual cites into a single string. + */ CSL.getBibliographyEntries = function (bibsection) { var ret, input, include, anymatch, allmatch, bib_entry, res, item, spec, lllen, pppos, topblobs, entry_item_ids, debug, collapse_parallel, i, ilen, siblings, skips, sortedItems, eyetem, entry_item_data, j, jlen; ret = []; @@ -6201,11 +8243,16 @@ CSL.getBibliographyEntries = function (bibsection) { this.tmp.last_rendered_name = false; this.tmp.bibliography_errors = []; this.tmp.bibliography_pos = 0; + + // For paged returns: disable generated entries and + // do not fetch full items as a batch (input variable + // consists of ids only in this case) if (bibsection && bibsection.page_start && bibsection.page_length) { input = this.registry.getSortedIds(); } else { input = this.refetchItems(this.registry.getSortedIds()); } + this.tmp.disambig_override = true; function eval_string(a, b) { if (a === b) { @@ -6239,7 +8286,10 @@ CSL.getBibliographyEntries = function (bibsection) { } } } + skips = {}; + + // For paged returns var page_item_count; if (bibsection && bibsection.page_start && bibsection.page_length) { page_item_count = 0; @@ -6252,8 +8302,12 @@ CSL.getBibliographyEntries = function (bibsection) { } } } + var processed_item_ids = []; + for (i = 0, ilen = input.length; i < ilen; i += 1) { + + // For paged returns if (bibsection && bibsection.page_start && bibsection.page_length) { if (skips[input[i]]) { continue; @@ -6271,6 +8325,9 @@ CSL.getBibliographyEntries = function (bibsection) { if (bibsection) { include = true; if (bibsection.include) { + // + // Opt-in: these are OR-ed. + // include = false; for (j = 0, jlen = bibsection.include.length; j < jlen; j += 1) { spec = bibsection.include[j]; @@ -6280,6 +8337,9 @@ CSL.getBibliographyEntries = function (bibsection) { } } } else if (bibsection.exclude) { + // + // Opt-out: these are also OR-ed. + // anymatch = false; for (j = 0, jlen = bibsection.exclude.length; j < jlen; j += 1) { spec = bibsection.exclude[j]; @@ -6292,6 +8352,9 @@ CSL.getBibliographyEntries = function (bibsection) { include = false; } } else if (bibsection.select) { + // + // Multiple condition opt-in: these are AND-ed. + // include = false; allmatch = true; for (j = 0, jlen = bibsection.select.length; j < jlen; j += 1) { @@ -6305,6 +8368,9 @@ CSL.getBibliographyEntries = function (bibsection) { } } if (bibsection.quash) { + // + // Stop criteria: These are AND-ed. + // allmatch = true; for (j = 0, jlen = bibsection.quash.length; j < jlen; j += 1) { spec = bibsection.quash[j]; @@ -6320,6 +8386,11 @@ CSL.getBibliographyEntries = function (bibsection) { continue; } } + //SNIP-START + if (debug) { + CSL.debug("BIB: " + item.id); + } + //SNIP-END bib_entry = new CSL.Token("group", CSL.START); bib_entry.decorations = [["@bibliography", "entry"]].concat(this.bibliography.opt.layout_decorations); this.output.startTag("bib_entry", bib_entry); @@ -6328,38 +8399,61 @@ CSL.getBibliographyEntries = function (bibsection) { } else { this.output.current.value().system_id = item.id; } - sortedItems = [[{id: "" + item.id}, item]]; + + // 2019-06-25 Hacked to conform to new parallels evaluation method entry_item_ids = []; if (this.registry.registry[item.id].master && !(bibsection && bibsection.page_start && bibsection.page_length)) { + + sortedItems = [[item, {id: item.id}]]; + var siblings = this.registry.registry[item.id].siblings; + for (var j=0,jlen=siblings.length; j init <--"); + } + //SNIP-END this.registry.init(idList); + if (rerun_ambigs) { for (var ambig in this.registry.ambigcites) { this.registry.ambigsTouched[ambig] = true; } } + this.registry.dodeletes(this.registry.myhash); + this.registry.doinserts(this.registry.mylist); + this.registry.dorefreshes(); + + // *** affects reflist this.registry.rebuildlist(nosort); + this.registry.setsortkeys(); + + // taints always this.registry.setdisambigs(); + + // *** affects reflist this.registry.sorttokens(nosort); + + // *** affects reflist + // taints if numbered style this.registry.renumber(); + + // taints always + //this.registry.yearsuffix(); + this.tmp.extension = oldExtension; this.tmp.area = oldArea; this.tmp.root = oldRoot; + return this.registry.getSortedIds(); }; + CSL.Engine.prototype.updateUncitedItems = function (idList, nosort) { var idHash; var oldArea = this.tmp.area; @@ -6572,6 +8757,7 @@ CSL.Engine.prototype.updateUncitedItems = function (idList, nosort) { this.tmp.root = "citation"; this.tmp.extension = ""; this.tmp.loadedItemIDs = {}; + // This should be a utility function if (!idList) { idList = []; } @@ -6589,20 +8775,38 @@ CSL.Engine.prototype.updateUncitedItems = function (idList, nosort) { } } } + + // prepare extended list of items this.registry.init(idList, true); + + // Use purge instead of delete. + // this.registry.dodeletes(this.registry.myhash); this.registry.dopurge(idHash); + + // everything else is the same as updateItems() this.registry.doinserts(this.registry.mylist); + this.registry.dorefreshes(); + this.registry.rebuildlist(nosort); + this.registry.setsortkeys(); + this.registry.setdisambigs(); + this.registry.sorttokens(nosort); + this.registry.renumber(); + this.tmp.extension = oldExtension; this.tmp.area = oldArea; this.tmp.root = oldRoot; + return this.registry.getSortedIds(); }; + +/*global CSL: true */ + CSL.localeResolve = function (langstr, defaultLocale) { var ret, langlst; if (!defaultLocale) { @@ -6612,9 +8816,13 @@ CSL.localeResolve = function (langstr, defaultLocale) { langstr = defaultLocale; } ret = {}; + //if ("undefined" === typeof langstr) { + // langstr = "en_US"; + //} langlst = langstr.split(/[\-_]/); ret.base = CSL.LANG_BASES[langlst[0]]; if ("undefined" === typeof ret.base) { + //CSL.debug("Warning: unknown locale "+langstr+", setting fallback to "+defaultLocale); return {base:defaultLocale, best:langstr, bare:langlst[0]}; } if (langlst.length === 1) { @@ -6629,6 +8837,8 @@ CSL.localeResolve = function (langstr, defaultLocale) { ret.bare = langlst[0]; return ret; }; + +// Use call to invoke this. CSL.Engine.prototype.localeConfigure = function (langspec, beShy) { var localexml; if (beShy && this.locale[langspec.best]) { @@ -6674,39 +8884,64 @@ CSL.Engine.prototype.localeConfigure = function (langspec, beShy) { this.opt.lang = this.opt.lang.toLowerCase(); } }; + +// +// XXXXX: Got it. The locales objects need to be reorganized, +// with a top-level local specifier, and terms, opts, dates +// below. +// CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { var blob, locale, nodes, attributes, pos, term, form, termname, styleopts, date, attrname, len, genderform, target, i, ilen; lang_in = lang_in.replace("_", "-"); lang_out = lang_out.replace("_", "-"); + if (this.opt.development_extensions.normalize_lang_keys_to_lowercase) { lang_in = lang_in.toLowerCase(); lang_out = lang_out.toLowerCase(); } + if (!this.locale[lang_out]) { this.locale[lang_out] = {}; this.locale[lang_out].terms = {}; this.locale[lang_out].opts = {}; + // Set default skip words. Can be overridden in locale by attribute on style-options node. this.locale[lang_out].opts["skip-words"] = CSL.SKIP_WORDS; + // Initialise leading noise word to false. Actual assignment is below. Empty by default, can be overridden in locale by attribute on style-options node. if (!this.locale[lang_out].opts["leading-noise-words"]) { this.locale[lang_out].opts["leading-noise-words"] = []; } this.locale[lang_out].dates = {}; + // For ordinals this.locale[lang_out].ord = {'1.0.1':false,keys:{}}; this.locale[lang_out]["noun-genders"] = {}; } + + // + // Xml: Test if node is "locale" (nb: ns declarations need to be invoked + // on every access to the xml object; bundle this with the functions + // locale = myxml.makeXml(); if (myxml.nodeNameIs(myxml.dataObj, 'locale')) { locale = myxml.dataObj; } else { + // + // Xml: get a list of all "locale" nodes + // nodes = myxml.getNodesByName(myxml.dataObj, "locale"); for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) { blob = nodes[pos]; + // + // Xml: get locale xml:lang + // if (myxml.getAttributeValue(blob, 'lang', 'xml') === lang_in) { locale = blob; break; } } } + // + // Xml: get a list of any cs:type nodes within locale + // nodes = myxml.getNodesByName(locale, 'type'); for (i = 0, ilen = myxml.numberofnodes(nodes); i < ilen; i += 1) { var typenode = nodes[i]; @@ -6714,6 +8949,12 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { var gender = myxml.getAttributeValue(typenode, 'gender'); this.opt.gender[type] = gender; } + // + // Xml: get a list of term nodes within locale + // + + // If we are setting CSL 1.0.1 ordinals inside a style, wipe the + // slate clean and start over. var hasCslOrdinals101 = myxml.getNodesByName(locale, 'term', 'ordinal').length; if (hasCslOrdinals101) { for (var key in this.locale[lang_out].ord.keys) { @@ -6721,12 +8962,17 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { } this.locale[lang_out].ord = {"1.0.1":false,keys:{}}; } + nodes = myxml.getNodesByName(locale, 'term'); + // Collect ordinals info as for 1.0.1, but save only if 1.0.1 toggle triggers var ordinals101 = {"last-digit":{},"last-two-digits":{},"whole-number":{}}; var ordinals101_toggle = false; var genderized_terms = {}; for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) { term = nodes[pos]; + // + // Xml: get string value of attribute + // termname = myxml.getAttributeValue(term, 'name'); if (termname === "sub verbo") { termname = "sub-verbo"; @@ -6762,15 +9008,26 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { } form = "long"; genderform = false; + // + // Xml: get string value of form attribute, if any + // if (myxml.getAttributeValue(term, 'form')) { form = myxml.getAttributeValue(term, 'form'); } + // + // Xml: get string value of gender attribute, if any + // if (myxml.getAttributeValue(term, 'gender-form')) { genderform = myxml.getAttributeValue(term, 'gender-form'); } + // + // Xml: set global gender assignment for variable associated + // with term name + // if (myxml.getAttributeValue(term, 'gender')) { this.locale[lang_out]["noun-genders"][termname] = myxml.getAttributeValue(term, 'gender'); } + // Work on main segment or gender-specific sub-segment as appropriate if (genderform) { this.locale[lang_out].terms[termname][genderform] = {}; this.locale[lang_out].terms[termname][genderform][form] = []; @@ -6780,16 +9037,31 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { this.locale[lang_out].terms[termname][form] = []; target = this.locale[lang_out].terms[termname]; } + // + // Xml: test of existence of node + // if (myxml.numberofnodes(myxml.getNodesByName(term, 'multiple'))) { + // + // Xml: get string value of attribute, plus + // Xml: get string value of node content + // target[form][0] = myxml.getNodeValue(term, 'single'); if (target[form][0].indexOf("%s") > -1) { this.opt.hasPlaceholderTerm = true; } + // + // Xml: get string value of attribute, plus + // Xml: get string value of node content + // target[form][1] = myxml.getNodeValue(term, 'multiple'); if (target[form][1].indexOf("%s") > -1) { this.opt.hasPlaceholderTerm = true; } } else { + // + // Xml: get string value of attribute, plus + // Xml: get string value of node content + // target[form] = myxml.getNodeValue(term); if (target[form].indexOf("%s") > -1) { this.opt.hasPlaceholderTerm = true; @@ -6802,6 +9074,8 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { if (!this.locale[lang_out].terms.supplement["long"]) { this.locale[lang_out].terms.supplement["long"] = ["supplement", "supplements"]; } + // If locale had a CSL 1.0.1-style ordinal definition, install the logic object + // and iterate over gendered terms, filling in default values for use by getTerm. if (ordinals101_toggle) { for (var ikey in genderized_terms) { var gender_segments = {}; @@ -6815,10 +9089,13 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { } if (!form_segments) { if (gender_segments.feminine) { + // Link each feminine form segment to default + // (no need to filter, these will not have gender segments mixed in) for (var jkey in gender_segments.feminine) { this.locale[lang_out].terms[ikey][jkey] = gender_segments.feminine[jkey]; } } else if (gender_segments.masculine) { + // Otherwise link each masculine form segment to default for (var jkey in gender_segments.masculine) { this.locale[lang_out].terms[ikey][jkey] = gender_segments.masculine[jkey]; } @@ -6827,6 +9104,9 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { } this.locale[lang_out].ord['1.0.1'] = ordinals101; } + + // Iterate over main segments, and fill in any holes in gender-specific data + // sub-segments for (termname in this.locale[lang_out].terms) { for (i = 0, ilen = 2; i < ilen; i += 1) { genderform = CSL.GENDERS[i]; @@ -6839,21 +9119,29 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { } } } + // + // Xml: get list of nodes by node type + // nodes = myxml.getNodesByName(locale, 'style-options'); for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) { if (true) { styleopts = nodes[pos]; + // + // Xml: get list of attributes on a node + // attributes = myxml.attributes(styleopts); for (attrname in attributes) { if (attributes.hasOwnProperty(attrname)) { if (attrname === "@punctuation-in-quote" || attrname === "@limit-day-ordinals-to-day-1") { if (attributes[attrname] === "true") { + // trim off leading @ this.locale[lang_out].opts[attrname.slice(1)] = true; } else { + // trim off leading @ this.locale[lang_out].opts[attrname.slice(1)] = false; } } else if (attrname === "@jurisdiction-preference") { - var jurisdiction_preference = attributes[attrname].split(/\s*,\s*/); + var jurisdiction_preference = attributes[attrname].split(/\s+/); this.locale[lang_out].opts[attrname.slice(1)] = jurisdiction_preference; } else if (attrname === "@skip-words") { var skip_words = attributes[attrname].split(/\s*,\s*/); @@ -6862,18 +9150,21 @@ CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) { var val = attributes[attrname].split(/\s*,\s*/); this.locale[lang_out].opts["leading-noise-words"] = val; } else if (attrname === "@name-as-sort-order") { + // Fallback is okay here. this.locale[lang_out].opts["name-as-sort-order"] = {}; var lst = attributes[attrname].split(/\s+/); for (var i=0,ilen=lst.length;i -1) { + // + // Use ghost dateput queue + // if ("" + value_end !== "0") { + // + // XXXXX: It's a workaround. It's ugly. + // There's another one above. + // if (state.dateput.queue.length === 0) { first_date = true; } @@ -7301,6 +9777,7 @@ CSL.Node["date-part"] = { } else { state.output.append(value, this); } + if (bc) { state.output.append(bc); } @@ -7308,10 +9785,23 @@ CSL.Node["date-part"] = { state.output.append(ad); } state.output.closeLevel(); + //state.output.endTag(); } else if ("month" === this.strings.name) { + // XXX The simpler solution here will be to + // directly install season and season_end on + // month, with a value of 13, 14, 15, 16, or + // (to allow correct ranging with Down Under + // dates) 17 or 18. That will allow ranging + // to take place in the normal way. With this + // "approach", it doesn't. + // + // No value for this target variable + // if (state.tmp.date_object.season) { value = "" + state.tmp.date_object.season; if (value && value.match(/^[1-4]$/)) { + // XXXXXXXXXXXXXXXXXXX was replace([false, false, true]); + //state.tmp.group_context.replace([false, false, true]); state.tmp.group_context.tip.variable_success = true; state.output.append(state.getTerm(("season-0" + value)), this); } else if (value) { @@ -7324,6 +9814,7 @@ CSL.Node["date-part"] = { if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false && !state.tmp.has_done_year_suffix) { state.tmp.has_done_year_suffix = true; num = parseInt(state.registry.registry[Item.id].disambig.year_suffix, 10); + // first argument is for number particle [a-zA-Z], never present on dates number = new CSL.NumericBlob(false, num, this, Item.id); this.successor_prefix = state[state.build.area].opt.layout_delimiter; this.splice_prefix = state[state.build.area].opt.layout_delimiter; @@ -7343,12 +9834,20 @@ CSL.Node["date-part"] = { state.output.append(number, "literal"); } } + }; this.execs.push(func); target.push(this); } }; + + + +/*global CSL: true */ + CSL.Node["else-if"] = { + // + // these function are the same as those in if, might just clone build: function (state, target) { CSL.Conditions.TopNode.call(this, state, target); target.push(this); @@ -7357,6 +9856,9 @@ CSL.Node["else-if"] = { CSL.Conditions.Configure.call(this, state, pos); } }; + +/*global CSL: true */ + CSL.Node["else"] = { build: function (state, target) { target.push(this); @@ -7367,6 +9869,10 @@ CSL.Node["else"] = { } } }; + + +/*global CSL: true */ + CSL.Node["et-al"] = { build: function (state, target) { if (state.build.area === "citation" || state.build.area === "bibliography") { @@ -7381,6 +9887,9 @@ CSL.Node["et-al"] = { target.push(this); } }; + +/*global CSL: true */ + CSL.Node.group = { build: function (state, target, realGroup) { var func, execs, done_vars; @@ -7393,28 +9902,38 @@ CSL.Node.group = { if (!this.juris) { target.push(this); } + + // newoutput func = function (state) { state.output.startTag("group", this); + if (this.strings.label_form_override) { if (!state.tmp.group_context.tip.label_form) { state.tmp.group_context.tip.label_form = this.strings.label_form_override; } } + if (this.strings.label_capitalize_if_first_override) { if (!state.tmp.group_context.tip.label_capitalize_if_first) { state.tmp.group_context.tip.label_capitalize_if_first = this.strings.label_capitalize_if_first_override; } } + if (this.realGroup) { var condition = false; var force_suppress = false; + + // XXX Can we do something better for length here? if (state.tmp.group_context.mystack.length) { state.output.current.value().parent = state.tmp.group_context.tip.output_tip; } + + // fieldcontextflag var label_form = state.tmp.group_context.tip.label_form; if (!label_form) { label_form = this.strings.label_form_override; } + var label_capitalize_if_first = state.tmp.group_context.tip.label_capitalize_if_first; if (!label_capitalize_if_first) { label_capitalize_if_first = this.strings.label_capitalize_if_first; @@ -7422,6 +9941,7 @@ CSL.Node.group = { if (state.tmp.group_context.tip.condition) { condition = state.tmp.group_context.tip.condition; force_suppress = state.tmp.group_context.tip.force_suppress; + //force_suppress: false; } else if (this.strings.reject) { condition = { test: this.strings.reject, @@ -7436,6 +9956,14 @@ CSL.Node.group = { }; done_vars = []; } + // CONDITION + //if (!state.tmp.just_looking) { + // print(" pushing condition[" + state.tmp.group_context.mystack.length + "]: "+condition+" "+force_suppress); + //} + //if (!state.tmp.just_looking) { + // var params = ["variable_success", "force_suppress","term_intended", "variable_attempt"] + // print("PUSH parent="+JSON.stringify(state.tmp.group_context.tip, params)) + //} state.tmp.group_context.push({ old_term_predecessor: state.tmp.term_predecessor, term_intended: false, @@ -7445,19 +9973,38 @@ CSL.Node.group = { output_tip: state.output.current.tip, label_form: label_form, label_capitalize_if_first: label_capitalize_if_first, - parallel_conditions: this.strings.set_parallel_condition, + parallel_condition: this.strings.set_parallel_condition, + no_repeat_condition: this.strings.set_no_repeat_condition, + parallel_result: undefined, + parallel_repeats: undefined, condition: condition, force_suppress: force_suppress, done_vars: state.tmp.group_context.tip.done_vars.slice() }); + //if (!state.tmp.just_looking) { + // print(" flags="+JSON.stringify(state.tmp.group_context.tip, params)) + //} } }; + // + // Paranoia. Assure that this init function is the first executed. execs = []; execs.push(func); this.execs = execs.concat(this.execs); + + // "Special handling" for nodes that contain only + // publisher and place, with no affixes. For such + // nodes only, parallel publisher/place pairs + // will be parsed out and properly joined, piggybacking on + // join parameters set on cs:citation or cs:bibliography. if (this.strings["has-publisher-and-publisher-place"]) { + // Pass variable string values to the closing + // tag via a global, iff they conform to expectations. state.build["publisher-special"] = true; if (this.strings["subgroup-delimiter"]) { + // Set the handling function only if name-delimiter + // is set on the parent cs:citation or cs:bibliography + // node. func = function (state, Item) { if (Item.publisher && Item["publisher-place"]) { var publisher_lst = Item.publisher.split(/;\s*/); @@ -7473,18 +10020,48 @@ CSL.Node.group = { this.execs.push(func); } } + if (this.juris) { + // "Special handling" for jurisdiction macros + // We try to instantiate these as standalone token lists. + // If available, the token list is executed, + // the result is written directly into output, + // and control returns here. + + // So we'll have something like this: + // * expandMacro() in util_node.js flags juris- macros + // on build. [DONE] + // * Those are picked up here, and + // - A runtime function attempts to fetch and instantiate + // the macros in separate token lists under a segment + // opened for the jurisdiction. We assume that the + // jurisdiction has a full set of macros. That will need + // to be enforced by validation. [DONE HERE, function is TODO] + // - Success or failure is marked in a runtime flag object + // (in citeproc.opt). [DONE] + // - After the instantiation function comes a test, for + // juris- macros only, which either runs diverted code, + // or proceeds as per normal through the token list. [TODO] + // I think that's all there is to it. + + // Code for fetching an instantiating? + var choose_start = new CSL.Token("choose", CSL.START); CSL.Node.choose.build.call(choose_start, state, target); + var if_start = new CSL.Token("if", CSL.START); + func = (function (macroName) { return function (Item) { if (!state.sys.retrieveStyleModule || !CSL.MODULE_MACROS[macroName] || !Item.jurisdiction) { return false; } var jurisdictionList = state.getJurisdictionList(Item.jurisdiction); + // Set up a list of jurisdictions here, we will reuse it if (!state.opt.jurisdictions_seen[jurisdictionList[0]]) { var res = state.retrieveAllStyleModules(jurisdictionList); + // Okay. We have code for each of the novel modules in the + // hierarchy. Load them all into the processor. for (var jurisdiction in res) { var macroCount = 0; state.juris[jurisdiction] = {}; @@ -7512,11 +10089,17 @@ CSL.Node.group = { } macroCount++; state.juris[jurisdiction][myName] = []; + // Must use the same XML parser for style and modules. state.buildTokenLists(myNodes[i], state.juris[jurisdiction][myName]); state.configureTokenList(state.juris[jurisdiction][myName]); } + //if (macroCount < Object.keys(CSL.MODULE_MACROS).length) { + // var missing = []; + // throw "CSL ERROR: Incomplete jurisdiction style module for: " + jurisdiction; + //} } } + // Identify the best jurisdiction for the item and return true, otherwise return false for (var i=0,ilen=jurisdictionList.length;i -1) { state.tmp.done_vars = state.tmp.done_vars.slice(0, i).concat(state.tmp.done_vars.slice(i+1)); @@ -7612,6 +10221,7 @@ CSL.Node.group = { } }; this.execs.push(func); + if (this.juris) { var else_end = new CSL.Token("else", CSL.END); CSL.Node["else"].build.call(else_end, state, target); @@ -7619,6 +10229,7 @@ CSL.Node.group = { CSL.Node.choose.build.call(choose_end, state, target); } } + if (this.tokentype === CSL.END) { if (!this.juris) { target.push(this); @@ -7630,6 +10241,10 @@ CSL.Node.group = { } } }; + + +/*global CSL: true */ + CSL.Node["if"] = { build: function (state, target) { CSL.Conditions.TopNode.call(this, state, target); @@ -7639,6 +10254,8 @@ CSL.Node["if"] = { CSL.Conditions.Configure.call(this, state, pos); } }; + + CSL.Node["conditions"] = { build: function (state) { if (this.tokentype === CSL.START) { @@ -7649,6 +10266,7 @@ CSL.Node["conditions"] = { } } }; + CSL.Node["condition"] = { build: function (state) { if (this.tokentype === CSL.SINGLETON) { @@ -7657,7 +10275,9 @@ CSL.Node["condition"] = { } } }; + CSL.Conditions = {}; + CSL.Conditions.TopNode = function (state) { var func; if (this.tokentype === CSL.START || this.tokentype === CSL.SINGLETON) { @@ -7665,30 +10285,41 @@ CSL.Conditions.TopNode = function (state) { state.opt.lang = this.locale; } if (!this.tests || !this.tests.length) { + // Set up the condition compiler with our current context state.tmp.conditions = new CSL.Conditions.Engine(state, this); } else { + // The usual. this.test = state.fun.match[this.match](this, state, this.tests); } - func = function(state) { - state.tmp.condition_counter++; + if (state.build.substitute_level.value() === 0) { + func = function(state) { + state.tmp.condition_counter++; + } + this.execs.push(func); } - this.execs.push(func); } if (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON) { - func = function (state) { - state.tmp.condition_counter--; - if (state.tmp.condition_lang_counter_arr.length > 0) { - var counter = state.tmp.condition_lang_counter_arr.slice(-1)[0]; - if (counter === state.tmp.condition_counter) { - state.opt.lang = state.tmp.condition_lang_val_arr.pop(); - state.tmp.condition_lang_counter_arr.pop(); + if (state.build.substitute_level.value() === 0) { + func = function (state) { + state.tmp.condition_counter--; + if (state.tmp.condition_lang_counter_arr.length > 0) { + var counter = state.tmp.condition_lang_counter_arr.slice(-1)[0]; + if (counter === state.tmp.condition_counter) { + state.opt.lang = state.tmp.condition_lang_val_arr.pop(); + state.tmp.condition_lang_counter_arr.pop(); + } } - } - if (this.locale_default) { - state.output.current.value().old_locale = this.locale_default; - state.output.closeLevel("empty"); - state.opt.lang = this.locale_default; - } + if (this.locale_default) { + // Open output tag with locale marker + state.output.current.value().old_locale = this.locale_default; + state.output.closeLevel("empty"); + state.opt.lang = this.locale_default; + } + }; + this.execs.push(func); + } + // closingjump + func = function (state) { var next = this[state.tmp.jump.value()]; return next; }; @@ -7698,34 +10329,45 @@ CSL.Conditions.TopNode = function (state) { } } }; + CSL.Conditions.Configure = function (state, pos) { if (this.tokentype === CSL.START) { + // jump index on failure this.fail = state.configure.fail.slice(-1)[0]; this.succeed = this.next; state.configure.fail[(state.configure.fail.length - 1)] = pos; } else if (this.tokentype === CSL.SINGLETON) { + // jump index on failure this.fail = this.next; this.succeed = state.configure.succeed.slice(-1)[0]; state.configure.fail[(state.configure.fail.length - 1)] = pos; } else { + // jump index on success this.succeed = state.configure.succeed.slice(-1)[0]; this.fail = this.next; } }; + CSL.Conditions.Engine = function (state, token) { this.token = token; this.state = state; }; + CSL.Conditions.Engine.prototype.addTest = function (test) { this.token.tests ? {} : this.token.tests = []; this.token.tests.push(test); }; + CSL.Conditions.Engine.prototype.addMatch = function (match) { this.token.match = match; }; + CSL.Conditions.Engine.prototype.matchCombine = function () { this.token.test = this.state.fun.match[this.token.match](this.token, this.state, this.token.tests); }; + +/*global CSL: true */ + CSL.Node.info = { build: function (state) { if (this.tokentype === CSL.START) { @@ -7735,15 +10377,24 @@ CSL.Node.info = { } } }; + + +/*global CSL: true */ + CSL.Node.institution = { build: function (state, target) { if ([CSL.SINGLETON, CSL.START].indexOf(this.tokentype) > -1) { + var func = function (state) { if ("string" === typeof this.strings.delimiter) { state.tmp.institution_delimiter = this.strings.delimiter; } else { state.tmp.institution_delimiter = state.tmp.name_delimiter; } + + // This is the same code for the same result as in node_name.js, + // but when cs:institution comes on stream, it may produce + // different results. if ("text" === state.inheritOpt(this, "and")) { this.and_term = state.getTerm("and", "long", 0); } else if ("symbol" === state.inheritOpt(this, "and")) { @@ -7773,10 +10424,13 @@ CSL.Node.institution = { if (state.inheritOpt(this, "delimiter-precedes-last") === "always") { this.and_prefix_single = state.tmp.institution_delimiter; } else if (state.inheritOpt(this, "delimiter-precedes-last") === "never") { + // Slightly fragile: could test for charset here to make + // this more certain. if (this.and_prefix_multiple) { this.and_prefix_multiple = " "; } } + this.and = {}; if ("undefined" !== typeof this.and_term) { state.output.append(this.and_term, "empty", true); @@ -7807,6 +10461,9 @@ CSL.Node.institution = { } } }; + +/*global CSL: true */ + CSL.Node["institution-part"] = { build: function (state, target) { var func; @@ -7829,38 +10486,60 @@ CSL.Node["institution-part"] = { target.push(this); } }; + +/*global CSL: true */ + CSL.Node.key = { build: function (state, target) { + target = state[state.build.root + "_sort"].tokens; + var func; var debug = false; var start_key = new CSL.Token("key", CSL.START); + state.tmp.root = state.build.root; + + // The params object for build and runtime (tmp) really shouldn't have been separated. + // Oh, well. start_key.strings["et-al-min"] = state.inheritOpt(this, "et-al-min"); start_key.strings["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first"); start_key.strings["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last"); + + + // initialize done vars func = function (state) { state.tmp.done_vars = []; }; start_key.execs.push(func); + + // initialize output queue func = function (state) { state.output.openLevel("empty"); }; start_key.execs.push(func); + + // sort direction var sort_direction = []; if (this.strings.sort_direction === CSL.DESCENDING) { + //print("sort: descending on "+state.tmp.area); sort_direction.push(1); sort_direction.push(-1); } else { + //print("sort: ascending"); sort_direction.push(-1); sort_direction.push(1); } state[state.build.area].opt.sort_directions.push(sort_direction); + if (CSL.DATE_VARIABLES.indexOf(this.variables[0]) > -1) { state.build.date_key = true; } + + // et al init func = function (state) { state.tmp.sort_key_flag = true; + //print("== key node function =="); if (state.inheritOpt(this, "et-al-min")) { state.tmp["et-al-min"] = state.inheritOpt(this, "et-al-min"); } @@ -7869,17 +10548,25 @@ CSL.Node.key = { } if ("boolean" === typeof state.inheritOpt(this, "et-al-use-last")) { state.tmp["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last"); + //print(" set tmp et-al-use-last: "+this.strings["et-al-use-last"]) } }; start_key.execs.push(func); target.push(start_key); + + // + // ops to initialize the key's output structures if (this.variables.length) { var variable = this.variables[0]; if (CSL.NAME_VARIABLES.indexOf(variable) > -1) { + // + // Start tag var names_start_token = new CSL.Token("names", CSL.START); names_start_token.tokentype = CSL.START; names_start_token.variables = this.variables; CSL.Node.names.build.call(names_start_token, state, target); + // + // Name tag var name_token = new CSL.Token("name", CSL.SINGLETON); name_token.tokentype = CSL.SINGLETON; name_token.strings["name-as-sort-order"] = "all"; @@ -7888,9 +10575,13 @@ CSL.Node.key = { name_token.strings["et-al-min"] = state.inheritOpt(this, "et-al-min"); name_token.strings["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first"); CSL.Node.name.build.call(name_token, state, target); + // + // Institution tag var institution_token = new CSL.Token("institution", CSL.SINGLETON); institution_token.tokentype = CSL.SINGLETON; CSL.Node.institution.build.call(institution_token, state, target); + // + // End tag var names_end_token = new CSL.Token("names", CSL.END); names_end_token.tokentype = CSL.END; CSL.Node.names.build.call(names_end_token, state, target); @@ -7899,6 +10590,9 @@ CSL.Node.key = { single_text.strings.sort_direction = this.strings.sort_direction; single_text.dateparts = this.dateparts; if (CSL.NUMERIC_VARIABLES.indexOf(variable) > -1) { + // citation-number is virtualized. As a sort key it has no effect on registry + // sort order per se, but if set to DESCENDING, it reverses the sequence of numbers representing + // bib entries. if (variable === "citation-number") { func = function (state, Item) { if (state.tmp.area === "bibliography_sort") { @@ -7914,6 +10608,7 @@ CSL.Node.key = { var num = state.registry.registry[Item.id].seq; } if (num) { + // Code currently in util_number.js num = CSL.Util.padding("" + num); } state.output.append(num, this); @@ -7922,7 +10617,9 @@ CSL.Node.key = { func = function (state, Item) { var num = false; num = Item[variable]; + // XXX What if this is NaN? if (num) { + // Code currently in util_number.js num = CSL.Util.padding(num); } state.output.append(num, this); @@ -7952,18 +10649,41 @@ CSL.Node.key = { target.push(single_text); } } else { // macro + // + // if it's not a variable, it's a macro var token = new CSL.Token("text", CSL.SINGLETON); token.strings.sort_direction = this.strings.sort_direction; token.postponed_macro = this.postponed_macro; CSL.expandMacro.call(state, token, target); } + // + // ops to output the key string result to an array go + // on the closing "key" tag before it is pushed. + // Do not close the level. var end_key = new CSL.Token("key", CSL.END); + + // Eliminated at revision 1.0.159. + // Was causing non-fatal error "wanted empty but found group". + // Possible contributor to weird "PAGES" bug? + //func = function (state, Item) { + //state.output.closeLevel("empty"); + //}; + //end_key.execs.push(func); + + // store key for use func = function (state) { var keystring = state.output.string(state, state.output.queue); if (state.sys.normalizeUnicode) { keystring = state.sys.normalizeUnicode(keystring); } keystring = keystring ? (keystring.split(" ").join(state.opt.sort_sep) + state.opt.sort_sep) : ""; + //SNIP-START + if (debug) { + CSL.debug("keystring: " + keystring + " " + typeof keystring); + } + //print("keystring: (" + keystring + ") " + typeof keystring + " " + state.tmp.area); + //SNIP-END + //state.sys.print("keystring: (" + keystring + ") " + typeof keystring + " " + state.tmp.area); if ("" === keystring) { keystring = undefined; } @@ -7975,10 +10695,14 @@ CSL.Node.key = { state.tmp.value = []; }; end_key.execs.push(func); + + // Set year-suffix key on anything that looks like a date if (state.build.date_key) { if (state.build.area === "citation" && state.build.extension === "_sort") { + // ascending sort always state[state.build.area].opt.sort_directions.push([-1,1]); func = function (state, Item) { + // year-suffix Key var year_suffix = state.registry.registry[Item.id].disambig.year_suffix; if (!year_suffix) { year_suffix = 0; @@ -7990,7 +10714,14 @@ CSL.Node.key = { } state.build.date_key = false; } + + // reset key params func = function (state) { + // state.tmp.name_quash = new Object(); + + // XXX This should work, should be necessary, but doesn't and isn't. + //state.output.closeLevel("empty"); + state.tmp["et-al-min"] = undefined; state.tmp["et-al-use-first"] = undefined; state.tmp["et-al-use-last"] = undefined; @@ -8000,21 +10731,35 @@ CSL.Node.key = { target.push(end_key); } }; + +/*global CSL: true */ + CSL.Node.label = { build: function (state, target) { + if (this.strings.term) { + // Non-names labels var func = function (state, Item, item) { + // Must accomplish this without touching strings + // shared with the calling application: "sub verbo" + // and "sub-verbo" must both pass, as they stand. + //if (item && item.label === "sub verbo") { + // item.label = "sub-verbo"; + //} + // This is abstracted away, because the same + // logic must be run in cs:names. var termtxt = CSL.evaluateLabel(this, state, Item, item); if (item && this.strings.term === "locator") { - state.parallel.StartVariable("label"); - state.parallel.AppendToVariable(item.label); + item.section_form_override = this.strings.form; + } if (termtxt) { state.tmp.group_context.tip.term_intended = true; } CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, termtxt); if (termtxt.indexOf("%s") === -1) { + // ^ Suppress output here if we have an embedded term if (this.strings.capitalize_if_first) { if (!state.tmp.term_predecessor && !(state.opt["class"] === "in-text" && state.tmp.area === "citation")) { termtxt = CSL.Output.Formatters["capitalize-first"](state, termtxt); @@ -8022,15 +10767,14 @@ CSL.Node.label = { } state.output.append(termtxt, this); } - if (item && this.strings.term === "locator") { - state.parallel.CloseVariable(); - } }; this.execs.push(func); } else { if (!this.strings.form) { this.strings.form = "long"; } + // Names labels + // Picked up in names END var namevars = state.build.names_variables[state.build.names_variables.length-1]; var namelabels = state.build.name_label[state.build.name_label.length-1]; for (var i = 0, ilen = namevars.length; i < ilen; i += 1) { @@ -8051,9 +10795,13 @@ CSL.Node.label = { target.push(this); } }; + +/*global CSL: true */ + CSL.Node.layout = { build: function (state, target) { var func, prefix_token, suffix_token, tok; + function setSuffix() { if (state.build.area === "bibliography") { suffix_token = new CSL.Token("text", CSL.SINGLETON); @@ -8064,6 +10812,10 @@ CSL.Node.layout = { } else { suffix = state.bibliography.opt.layout_suffix; } + + // If @display is used, layout suffix is placed on the last + // immediate child of the layout, which we assume will be a + // @display group node. var topblob = state.output.current.value(); if (state.opt.using_display) { topblob.blobs[topblob.blobs.length-1].strings.suffix = suffix; @@ -8071,6 +10823,7 @@ CSL.Node.layout = { topblob.strings.suffix = suffix; } if (state.bibliography.opt["second-field-align"]) { + // closes bib_other state.output.endTag("bib_other"); } }; @@ -8078,18 +10831,22 @@ CSL.Node.layout = { target.push(suffix_token); } } + if (this.tokentype === CSL.START) { + if (this.locale_raw) { state.build.current_default_locale = this.locale_raw; } else { state.build.current_default_locale = state.opt["default-locale"]; } + func = function (state, Item, item) { if (state.opt.development_extensions.apply_citation_wrapper && state.sys.wrapCitationEntry && !state.tmp.just_looking && Item.system_id && state.tmp.area === "citation") { + var cite_entry = new CSL.Token("group", CSL.START); cite_entry.decorations = [["@cite", "entry"]]; state.output.startTag("cite_entry", cite_entry); @@ -8102,34 +10859,50 @@ CSL.Node.layout = { }; this.execs.push(func); } + + // XXX Works, but using state.tmp looks wrong here? We're in the build layer ... if (this.tokentype === CSL.START && !state.tmp.cite_affixes[state.build.area]) { + // + // done_vars is used to prevent the repeated + // rendering of variables + // + // initalize done vars func = function (state, Item) { + state.tmp.done_vars = []; if (state.opt.suppressedJurisdictions[Item["country"]] && Item["country"] && ["treaty", "patent"].indexOf(Item.type) === -1) { + state.tmp.done_vars.push("country"); } if (!state.tmp.just_looking && state.registry.registry[Item.id] && state.registry.registry[Item.id].parallel) { state.tmp.done_vars.push("first-reference-note-number"); } + //CSL.debug(" === init rendered_name === "); state.tmp.rendered_name = false; }; this.execs.push(func); + // set opt delimiter func = function (state) { + // just in case state.tmp.sort_key_flag = false; }; this.execs.push(func); + + // reset nameset counter [all nodes] func = function (state) { state.tmp.nameset_counter = 0; }; this.execs.push(func); + func = function (state, Item) { var tok = new CSL.Token(); state.output.openLevel(tok); }; this.execs.push(func); target.push(this); + if (state.build.area === "citation") { prefix_token = new CSL.Token("text", CSL.SINGLETON); func = function (state, Item, item) { @@ -8146,6 +10919,8 @@ CSL.Node.layout = { target.push(prefix_token); } } + + // Cast token to be used in one of the configurations below. var my_tok; if (this.locale_raw) { my_tok = new CSL.Token("dummy", CSL.START); @@ -8156,32 +10931,53 @@ CSL.Node.layout = { state.tmp.cite_affixes[state.build.area] = {}; } } + if (this.tokentype === CSL.START) { state.build.layout_flag = true; + + // Only run the following once, to set up the final layout node ... if (!this.locale_raw) { + // + // save out decorations for flipflop processing [final node only] + // state[state.tmp.area].opt.topdecor = [this.decorations]; state[(state.tmp.area + "_sort")].opt.topdecor = [this.decorations]; + state[state.build.area].opt.layout_prefix = this.strings.prefix; state[state.build.area].opt.layout_suffix = this.strings.suffix; state[state.build.area].opt.layout_delimiter = this.strings.delimiter; + state[state.build.area].opt.layout_decorations = this.decorations; + + // Only do this if we're running conditionals if (state.tmp.cite_affixes[state.build.area]) { + // if build_layout_locale_flag is true, + // write cs:else START to the token list. tok = new CSL.Token("else", CSL.START); CSL.Node["else"].build.call(tok, state, target); } + } // !this.locale_raw + + // Conditionals if (this.locale_raw) { if (!state.build.layout_locale_flag) { + // if layout_locale_flag is untrue, + // write cs:choose START and cs:if START + // to the token list. var choose_tok = new CSL.Token("choose", CSL.START); CSL.Node.choose.build.call(choose_tok, state, target); my_tok.name = "if"; CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw); CSL.Node["if"].build.call(my_tok, state, target); } else { + // if build_layout_locale_flag is true, + // write cs:else-if START to the token list. my_tok.name = "else-if"; CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw); CSL.Node["else-if"].build.call(my_tok, state, target); } + // cite_affixes for this node state.tmp.cite_affixes[state.build.area][my_tok.locale] = {}; state.tmp.cite_affixes[state.build.area][my_tok.locale].delimiter = this.strings.delimiter; state.tmp.cite_affixes[state.build.area][my_tok.locale].suffix = this.strings.suffix; @@ -8191,12 +10987,16 @@ CSL.Node.layout = { if (this.locale_raw) { setSuffix(); if (!state.build.layout_locale_flag) { + // If layout_locale_flag is untrue, write cs:if END + // to the token list. my_tok.name = "if"; my_tok.tokentype = CSL.END; CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw); CSL.Node["if"].build.call(my_tok, state, target); state.build.layout_locale_flag = true; } else { + // If layout_locale_flag is true, write cs:else-if END + // to the token list. my_tok.name = "else-if"; my_tok.tokentype = CSL.END; CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw); @@ -8205,7 +11005,10 @@ CSL.Node.layout = { } if (!this.locale_raw) { setSuffix(); + // Only add this if we're running conditionals if (state.tmp.cite_affixes[state.build.area]) { + // If layout_locale_flag is true, write cs:else END + // and cs:choose END to the token list. if (state.build.layout_locale_flag) { tok = new CSL.Token("else", CSL.END); CSL.Node["else"].build.call(tok, state, target); @@ -8229,6 +11032,8 @@ CSL.Node.layout = { suffix_token.execs.push(func); target.push(suffix_token); } + + // Closes wrapper token func = function (state) { state.output.closeLevel(); }; @@ -8239,6 +11044,7 @@ CSL.Node.layout = { && !state.tmp.just_looking && Item.system_id && state.tmp.area === "citation") { + state.output.endTag(); // closes citation link wrapper } }; @@ -8250,25 +11056,39 @@ CSL.Node.layout = { } } }; + +/*global CSL: true */ + CSL.Node.macro = { build: function () {} }; + +/*global CSL: true */ + CSL.Node.alternative = { build: function (state, target) { if (this.tokentype === CSL.START) { + var choose_tok = new CSL.Token("choose", CSL.START); CSL.Node["choose"].build.call(choose_tok, state, target); + var if_tok = new CSL.Token("if", CSL.START); CSL.Attributes["@alternative-node-internal"].call(if_tok, state); CSL.Node["if"].build.call(if_tok, state, target); + var func = function(state, Item) { + state.tmp.oldItem = Item; state.tmp.oldLang = state.opt.lang; state.tmp.abort_alternative = true; + if (Item["language-name"] && Item["language-name-original"]) { + var newItem = JSON.parse(JSON.stringify(Item)); + newItem.language = newItem["language-name"]; var langspec = CSL.localeResolve(newItem.language, state.opt["default-locale"][0]); + if (state.opt.multi_layout) { for (var i in state.opt.multi_layout) { var locale_list = state.opt.multi_layout[i]; @@ -8286,6 +11106,7 @@ CSL.Node.alternative = { state.opt.lang = gotlang; } } + for (var key in newItem) { if (["id", "type", "language", "multi"].indexOf(key) === -1 && key.slice(0, 4) !== "alt-") { if (newItem.multi && newItem.multi._keys[key]) { @@ -8326,14 +11147,17 @@ CSL.Node.alternative = { } } } + state.output.openLevel(this); state.registry.refhash[Item.id] = newItem; state.nameOutput = new CSL.NameOutput(state, newItem); }; this.execs.push(func); target.push(this); + var choose_tok = new CSL.Token("choose", CSL.START); CSL.Node["choose"].build.call(choose_tok, state, target); + var if_tok = new CSL.Token("if", CSL.START); CSL.Attributes["@alternative-node-internal"].call(if_tok, state); var func = function(state) { @@ -8341,11 +11165,15 @@ CSL.Node.alternative = { } if_tok.execs.push(func); CSL.Node["if"].build.call(if_tok, state, target); + } else if (this.tokentype === CSL.END) { + var if_tok = new CSL.Token("if", CSL.END); CSL.Node["if"].build.call(if_tok, state, target); + var choose_tok = new CSL.Token("choose", CSL.END); CSL.Node["choose"].build.call(choose_tok, state, target); + var func = function(state, Item) { state.output.closeLevel(); state.registry.refhash[Item.id] = state.tmp.oldItem; @@ -8355,16 +11183,21 @@ CSL.Node.alternative = { }; this.execs.push(func); target.push(this); + var if_tok = new CSL.Token("if", CSL.END); CSL.Node["if"].build.call(if_tok, state, target); + var choose_tok = new CSL.Token("choose", CSL.END); CSL.Node["choose"].build.call(choose_tok, state, target); + } } }; + CSL.Node["alternative-text"] = { build: function (state, target) { if (this.tokentype === CSL.SINGLETON) { + // do stuff var func = function(state, Item) { var Item = state.refetchItem(Item.id); CSL.getCite.call(state, Item); @@ -8374,8 +11207,18 @@ CSL.Node["alternative-text"] = { target.push(this); } }; + + + +/*global CSL: true */ + CSL.NameOutput = function(state, Item, item) { this.debug = false; + //SNIP-START + if (this.debug) { + print("(1)"); + } + //SNIP-END this.state = state; this.Item = Item; this.item = item; @@ -8384,6 +11227,7 @@ CSL.NameOutput = function(state, Item, item) { this._first_creator_variable = false; this._please_chop = false; }; + CSL.NameOutput.prototype.init = function (names) { this.requireMatch = names.requireMatch; if (this.state.tmp.term_predecessor) { @@ -8395,6 +11239,7 @@ CSL.NameOutput.prototype.init = function (names) { this.nameset_offset = 0; this.names = names; this.variables = names.variables; + this.state.tmp.value = []; this.state.tmp.rendered_name = []; this.state.tmp.label_blob = false; @@ -8406,18 +11251,32 @@ CSL.NameOutput.prototype.init = function (names) { } } this["et-al"] = undefined; + // REMOVE THIS this["with"] = undefined; + this.name = undefined; + // long, long-with-short, short this.institutionpart = {}; + // family, given + //this.namepart = {}; + // before, after + //this.label = {}; + this.state.tmp.group_context.tip.variable_attempt = true; + this.labelVariable = this.variables[0]; + if (!this.state.tmp.value.length) { return; } + + // Abort and proceed to the next substitution if a match is required, + // two variables are called, and they do not match. var checkCommonTerm = this.checkCommonAuthor(this.requireMatch); if (checkCommonTerm) { this.state.tmp.can_substitute.pop(); this.state.tmp.can_substitute.push(true); + //this.state.tmp.group_context.mystack[this.state.tmp.group_context.mystack.length-1].variable_success = false; for (var i in this.variables) { var idx = this.state.tmp.done_vars.indexOf(this.variables[i]); if (idx > -1) { @@ -8428,14 +11287,23 @@ CSL.NameOutput.prototype.init = function (names) { this.variables = []; } }; + + CSL.NameOutput.prototype.reinit = function (names, labelVariable) { this.requireMatch = names.requireMatch; this.labelVariable = labelVariable; + if (this.state.tmp.can_substitute.value()) { this.nameset_offset = 0; + // What-all should be carried across from the subsidiary + // names node, and on what conditions? For each attribute, + // and decoration, is it an override, or is it additive? this.variables = names.variables; + + // Not sure why this is necessary. Guards against a memory leak perhaps? var oldval = this.state.tmp.value.slice(); this.state.tmp.value = []; + for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) { if (this.Item[this.variables[i]] && this.Item[this.variables[i]].length) { this.state.tmp.value = this.state.tmp.value.concat(this.Item[this.variables[i]]); @@ -8444,8 +11312,12 @@ CSL.NameOutput.prototype.reinit = function (names, labelVariable) { if (this.state.tmp.value.length) { this.state.tmp.can_substitute.replace(false, CSL.LITERAL); } + this.state.tmp.value = oldval; + } + // Abort and proceed to the next substitution if a match is required, + // two variables are called, and they do not match. var checkCommonTerm = this.checkCommonAuthor(this.requireMatch); if (checkCommonTerm) { this.state.tmp.can_substitute.pop(); @@ -8459,6 +11331,7 @@ CSL.NameOutput.prototype.reinit = function (names, labelVariable) { this.variables = []; } }; + CSL.NameOutput.prototype.outputNames = function () { var i, ilen; var variables = this.variables; @@ -8470,32 +11343,78 @@ CSL.NameOutput.prototype.outputNames = function () { this.institution.and.multiple.blobs = this.name.and.multiple.blobs; } } + this.variable_offset = {}; if (this.family) { this.family_decor = CSL.Util.cloneToken(this.family); this.family_decor.strings.prefix = ""; this.family_decor.strings.suffix = ""; + // Sets text-case value (text-case="title" is suppressed for items + // non-English with non-English value in Item.language) for (i = 0, ilen = this.family.execs.length; i < ilen; i += 1) { this.family.execs[i].call(this.family_decor, this.state, this.Item); } } else { this.family_decor = false; } + if (this.given) { this.given_decor = CSL.Util.cloneToken(this.given); this.given_decor.strings.prefix = ""; this.given_decor.strings.suffix = ""; + // Sets text-case value (text-case="title" is suppressed for items + // non-English with non-English value in Item.language) for (i = 0, ilen = this.given.execs.length; i < ilen; i += 1) { this.given.execs[i].call(this.given_decor, this.state, this.Item); } } else { this.given_decor = false; } + + //SNIP-START + if (this.debug) { + print("(2)"); + } + //SNIP-END + // util_names_etalconfig.js this.getEtAlConfig(); + //SNIP-START + if (this.debug) { + print("(3)"); + } + //SNIP-END + // util_names_divide.js this.divideAndTransliterateNames(); + //SNIP-START + if (this.debug) { + print("(4)"); + } + //SNIP-END + // util_names_truncate.js + this.truncatePersonalNameLists(); + //SNIP-START + if (this.debug) { + print("(5)"); + } + //SNIP-END + + //SNIP-START + if (this.debug) { + print("(6)"); + } + //SNIP-END + // util_names_disambig.js this.disambigNames(); + + // util_names_constraints.js this.constrainNames(); + //SNIP-START + if (this.debug) { + print("(7)"); + } + //SNIP-END + // form="count" if (this.name.strings.form === "count") { if (this.state.tmp.extension || this.names_count != 0) { this.state.output.append(this.names_count, "empty"); @@ -8503,11 +11422,32 @@ CSL.NameOutput.prototype.outputNames = function () { } return; } + + //SNIP-START + if (this.debug) { + print("(8)"); + } + //SNIP-END this.setEtAlParameters(); + //SNIP-START + if (this.debug) { + print("(9)"); + } + //SNIP-END this.setCommonTerm(this.requireMatch); + //SNIP-START + if (this.debug) { + print("(10)"); + } + //SNIP-END this.state.tmp.name_node = {}; this.state.tmp.name_node.children = []; this.renderAllNames(); + //SNIP-START + if (this.debug) { + print("(11)"); + } + //SNIP-END var blob_list = []; for (i = 0, ilen = variables.length; i < ilen; i += 1) { var v = variables[i]; @@ -8517,9 +11457,19 @@ CSL.NameOutput.prototype.outputNames = function () { if (!this.state.opt.development_extensions.spoof_institutional_affiliations) { varblob = this._join([this.freeters[v]], ""); } else { + //SNIP-START + if (this.debug) { + print("(11a)"); + } + //SNIP-END 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]])); } + //SNIP-START + if (this.debug) { + print("(11b)"); + } + //SNIP-END if (this.institutions[v].length) { var pos = this.nameset_base + this.variable_offset[v]; if (this.freeters[v].length) { @@ -8527,35 +11477,94 @@ CSL.NameOutput.prototype.outputNames = function () { } institutions = this.joinInstitutionSets(institution_sets, pos); } + //SNIP-START + if (this.debug) { + print("(11c)"); + } + //SNIP-END var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]); + //SNIP-START + if (this.debug) { + print("(11d)"); + } + //SNIP-END } if (varblob) { + // Apply labels, if any if (!this.state.tmp.extension) { varblob = this._applyLabels(varblob, v); } blob_list.push(varblob); } + //SNIP-START + if (this.debug) { + print("(11e)"); + } + //SNIP-END if (this.common_term) { break; } } + //SNIP-START + if (this.debug) { + print("(12)"); + } + //SNIP-END this.state.output.openLevel("empty"); this.state.output.current.value().strings.delimiter = this.state.inheritOpt(this.names, "delimiter", "names-delimiter"); + //SNIP-START + if (this.debug) { + print("(13)"); + } + //SNIP-END for (i = 0, ilen = blob_list.length; i < ilen; i += 1) { + // notSerious this.state.output.append(blob_list[i], "literal", true); } if (!this.state.tmp.just_looking && blob_list.length > 0) { this.state.tmp.probably_rendered_something = true; } + //SNIP-START + if (this.debug) { + print("(14)"); + } + //SNIP-END this.state.output.closeLevel("empty"); + //SNIP-START + if (this.debug) { + print("(15)"); + } + //SNIP-END var blob = this.state.output.pop(); this.state.tmp.name_node.top = blob; + //SNIP-START + if (this.debug) { + print("(16)"); + } + //SNIP-END + + // Append will drop the names on the floor here if suppress-me is + // set on element_trace. + // Need to rescue the value for collapse comparison. var namesToken = CSL.Util.cloneToken(this.names); this.state.output.append(blob, namesToken); if (this.state.tmp.term_predecessor_name) { this.state.tmp.term_predecessor = true; } + //SNIP-START + if (this.debug) { + print("(17)"); + } + //SNIP-END + // Also used in CSL.Util.substituteEnd (which could do with + // some cleanup at this writing). + //SNIP-START + if (this.debug) { + print("(18)"); + } + //SNIP-END if (variables[0] !== "authority") { + // Just grab the string values in the name var name_node_string = []; var nameobjs = this.Item[variables[0]]; if (nameobjs) { @@ -8571,6 +11580,13 @@ CSL.NameOutput.prototype.outputNames = function () { this.state.tmp.name_node.string = name_node_string; } } + // for classic support + // This may be more convoluted than it needs to be. Or maybe not. + // + // Check for classic abbreviation + // + // If found, then (1) suppress title rendering, (2) replace the node + // with the abbreviation output [and (3) do not run this._collapseAuthor() ?] if (this.state.tmp.name_node.string && !this.state.tmp.first_name_string) { this.state.tmp.first_name_string = this.state.tmp.name_node.string; } @@ -8594,9 +11610,19 @@ CSL.NameOutput.prototype.outputNames = function () { } } } + + // Let's try something clever here. this._collapseAuthor(); + + // For name_SubstituteOnNamesSpanNamesSpanFail this.variables = []; + //SNIP-START + if (this.debug) { + print("(19)"); + } + //SNIP-END }; + CSL.NameOutput.prototype._applyLabels = function (blob, v) { var txt; if (!this.label || !this.label[this.labelVariable]) { @@ -8614,6 +11640,7 @@ CSL.NameOutput.prototype._applyLabels = function (blob, v) { plural = 1; } } + // Some code duplication here, should be factored out. if (this.label[this.labelVariable].before) { if ("number" === typeof this.label[this.labelVariable].before.strings.plural) { plural = this.label[this.labelVariable].before.strings.plural; @@ -8639,10 +11666,12 @@ CSL.NameOutput.prototype._applyLabels = function (blob, v) { } return blob; }; + CSL.NameOutput.prototype._buildLabel = function (term, plural, position, v) { if (this.common_term) { term = this.common_term; } + var ret = false; var node = this.label[v][position]; if (node) { @@ -8650,8 +11679,12 @@ CSL.NameOutput.prototype._buildLabel = function (term, plural, position, v) { } return ret; }; + + CSL.NameOutput.prototype._collapseAuthor = function () { var myqueue, mystr, oldchars; + // collapse can be undefined, an array of length zero, and probably + // other things ... ugh. if (this.state.tmp.name_node.top.blobs.length === 0) { return; } @@ -8662,17 +11695,22 @@ CSL.NameOutput.prototype._collapseAuthor = function () { && this.state[this.state.tmp.area].opt.collapse.length) || (this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter.length)) { + if (this.state.tmp.authorstring_request) { + // Avoid running this on every call to getAmbiguousCite()? mystr = ""; myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs; oldchars = this.state.tmp.offset_characters; if (myqueue) { mystr = this.state.output.string(this.state, myqueue, false); } + // Avoid side-effects on character counting: we're only interested + // in the final rendering. 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 && ((this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length) || this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter)) { + // XX1 print("RENDER: "+this.Item.id); mystr = ""; myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs; oldchars = this.state.tmp.offset_characters; @@ -8681,22 +11719,36 @@ CSL.NameOutput.prototype._collapseAuthor = function () { } if (mystr === this.state.tmp.last_primary_names_string) { if (this.item["suppress-author"] || (this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length)) { + // XX1 print(" CUT!"); this.state.tmp.name_node.top.blobs.pop(); this.state.tmp.name_node.children = []; + // If popped, avoid side-effects on character counting: we're only interested + // in things that actually render. this.state.tmp.offset_characters = oldchars; } + // Needed if (this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter) { this.state.tmp.use_cite_group_delimiter = true; } } else { + // XX1 print("remembering: "+mystr); this.state.tmp.last_primary_names_string = mystr; + // XXXXX A little more precision would be nice. + // This will clobber variable="author editor" as well as variable="author". + if (this.variables.indexOf(this._first_creator_variable) > -1 && this.item && this.item["suppress-author"] && this.Item.type !== "legal_case") { this.state.tmp.name_node.top.blobs.pop(); this.state.tmp.name_node.children = []; + // If popped, avoid side-effects on character counting: we're only interested + // in things that actually render. this.state.tmp.offset_characters = oldchars; + + // A wild guess, but will usually be correct this.state.tmp.term_predecessor = false; } + // Arcane and probably unnecessarily complicated? this.state.tmp.have_collapsed = false; + // Needed if (this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter) { this.state.tmp.use_cite_group_delimiter = false; } @@ -8704,25 +11756,45 @@ CSL.NameOutput.prototype._collapseAuthor = function () { } } }; + +/* +CSL.NameOutput.prototype.suppressNames = function() { + suppress_condition = suppress_min && display_names.length >= suppress_min; + if (suppress_condition) { + continue; + } +} +*/ + +/*global CSL: true */ + CSL.NameOutput.prototype.isPerson = function (value) { if (value.literal || (!value.given && value.family && value.isInstitution)) { + return false; } else { return true; } }; + +/*global CSL: true */ + CSL.NameOutput.prototype.truncatePersonalNameLists = function () { var v, i, ilen, j, jlen, chopvar, values; + // XXX Before truncation, make a note of the original number + // of names, for use in et-al evaluation. this.freeters_count = {}; this.persons_count = {}; this.institutions_count = {}; + // By key is okay here, as we don't care about sequence. for (v in this.freeters) { if (this.freeters.hasOwnProperty(v)) { this.freeters_count[v] = this.freeters[v].length; this.freeters[v] = this._truncateNameList(this.freeters, v); } } + for (v in this.persons) { if (this.persons.hasOwnProperty(v)) { this.institutions_count[v] = this.institutions[v].length; @@ -8735,6 +11807,7 @@ CSL.NameOutput.prototype.truncatePersonalNameLists = function () { } } } + // Could be factored out to a separate function for clarity. if (this.etal_min === 1 && this.etal_use_first === 1 && !(this.state.tmp.extension || this.state.tmp.just_looking)) { @@ -8790,6 +11863,36 @@ CSL.NameOutput.prototype.truncatePersonalNameLists = function () { } } } + + // Transliteration and abbreviation mapping + + // Hmm. This could produce three lists for each nameset: + // - primary (transformed in place) + // - secondary + // - tertiary + // with items that produce no result in the secondary and tertiary + // transforms set to false. Maybe. + + // Actually that would be insane, so forget it. + // What we need is to add suitable parameters to getName(), and merge + // the single-name-level operations below into that function. Then the + // operation can be applied in util_names_render.js, and the logic + // becomes very similar to what we already have running in util_transform.js. + +/* + for (v in this.freeters) { + this._transformNameset(this.freeters[v]); + } + for (v in this.persons) { + for (i = 0, ilen = this.persons[v].length; i < ilen; i += 1) { + this._transformNameset(this.persons[v][i]); + } + this._transformNameset(this.institutions[v]); + } +*/ + + // Could also be factored out to a separate function for clarity. + // ???? XXX Does this belong? for (i = 0, ilen = this.variables.length; i < ilen; i += 1) { if (this.institutions[v].length) { this.nameset_offset += 1; @@ -8798,9 +11901,11 @@ CSL.NameOutput.prototype.truncatePersonalNameLists = function () { if (this.persons[v][j].length) { this.nameset_offset += 1; } + // this.institutions[v][i] = this._splitInstitution(this.institutions[v][i], v, i); } } }; + CSL.NameOutput.prototype._truncateNameList = function (container, variable, index) { var lst; if ("undefined" === typeof index) { @@ -8811,11 +11916,17 @@ CSL.NameOutput.prototype._truncateNameList = function (container, variable, inde if (this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names && lst.length > 50 && lst.length > (this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names + 2)) { + + // Preserve the last name in the list, in case we're rendering with a PI ellipsis (et-al-use-last) var limit = this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names; lst = lst.slice(0, limit+1).concat(lst.slice(-1)); } return lst; }; + + +/*global CSL: true */ + CSL.NameOutput.prototype.divideAndTransliterateNames = function () { var i, ilen, j, jlen; var Item = this.Item; @@ -8855,6 +11966,7 @@ CSL.NameOutput.prototype.divideAndTransliterateNames = function () { } } }; + CSL.NameOutput.prototype._normalizeVariableValue = function (Item, variable) { var names; if ("string" === typeof Item[variable] || "number" === typeof Item[variable]) { @@ -8871,6 +11983,7 @@ CSL.NameOutput.prototype._normalizeVariableValue = function (Item, variable) { } return names; }; + CSL.NameOutput.prototype._getFreeters = function (v, values) { this.freeters[v] = []; if (this.state.opt.development_extensions.spoof_institutional_affiliations) { @@ -8898,6 +12011,7 @@ CSL.NameOutput.prototype._getFreeters = function (v, values) { this.nameset_offset += 1; } }; + CSL.NameOutput.prototype._getPersonsAndInstitutions = function (v, values) { this.persons[v] = []; this.institutions[v] = []; @@ -8931,11 +12045,13 @@ CSL.NameOutput.prototype._getPersonsAndInstitutions = function (v, values) { this.institutions[v].reverse(); } }; + CSL.NameOutput.prototype._clearValues = function (values) { for (var i = values.length - 1; i > -1; i += -1) { values.pop(); } }; + CSL.NameOutput.prototype._checkNickname = function (name) { if (["interview", "personal_communication"].indexOf(this.Item.type) > -1) { var author = ""; @@ -8943,9 +12059,11 @@ CSL.NameOutput.prototype._checkNickname = function (name) { if (author && this.state.sys.getAbbreviation && !(this.item && this.item["suppress-author"])) { var normalizedKey = author; if (this.state.sys.normalizeAbbrevsKey) { + // The first argument does not have to be the exact variable name. normalizedKey = this.state.sys.normalizeAbbrevsKey("author", author); } this.state.transform.loadAbbreviation("default", "nickname", normalizedKey); + // XXX Why does this have to happen here? var myLocalName = this.state.transform.abbrevs["default"].nickname[normalizedKey]; if (myLocalName) { if (myLocalName === "!here>>>") { @@ -8958,6 +12076,9 @@ CSL.NameOutput.prototype._checkNickname = function (name) { } return name; }; + +/*global CSL: true */ + CSL.NameOutput.prototype.joinPersons = function (blobs, pos, j, tokenname) { var ret; if (!tokenname) { @@ -8986,6 +12107,8 @@ CSL.NameOutput.prototype.joinPersons = function (blobs, pos, j, tokenname) { } return ret; }; + + CSL.NameOutput.prototype.joinInstitutionSets = function (blobs, pos) { var ret; if (this.etal_spec[pos].institutions === 1) { @@ -8997,16 +12120,30 @@ CSL.NameOutput.prototype.joinInstitutionSets = function (blobs, pos) { } return ret; }; + + CSL.NameOutput.prototype.joinPersonsAndInstitutions = function (blobs) { + // return this._join(blobs, this.state.tmp.name_delimiter); }; + +// LEGACY +// This should go away eventually CSL.NameOutput.prototype.joinFreetersAndInstitutionSets = function (blobs) { + // Nothing, one or two, never more var ret = this._join(blobs, "[never here]", this["with"].single, this["with"].multiple); + //var ret = this._join(blobs, ""); return ret; }; + CSL.NameOutput.prototype._joinEtAl = function (blobs, tokenname) { + // var blob = this._join(blobs, this.state.tmp.name_delimiter); + + // notSerious this.state.output.openLevel(this._getToken(tokenname)); + // Delimiter is applied from separately saved source in this case, + // for discriminate application of single and multiple joins. this.state.output.current.value().strings.delimiter = ""; this.state.output.append(blob, "literal", true); if (blobs.length > 1) { @@ -9017,25 +12154,35 @@ CSL.NameOutput.prototype._joinEtAl = function (blobs, tokenname) { this.state.output.closeLevel(); return this.state.output.pop(); }; + + CSL.NameOutput.prototype._joinEllipsis = function (blobs, tokenname) { return this._join(blobs, this.state.tmp.name_delimiter, this.name.ellipsis.single, this.name.ellipsis.multiple, tokenname); }; + + CSL.NameOutput.prototype._joinAnd = function (blobs, tokenname) { return this._join(blobs, this.state.inheritOpt(this[tokenname], "delimiter", (tokenname + "-delimiter"), ", "), this[tokenname].and.single, this[tokenname].and.multiple, tokenname); }; + + CSL.NameOutput.prototype._join = function (blobs, delimiter, single, multiple) { var i, ilen; if (!blobs) { return false; } + // Eliminate false and empty blobs for (i = blobs.length - 1; i > -1; i += -1) { if (!blobs[i] || blobs[i].length === 0 || !blobs[i].blobs.length) { blobs = blobs.slice(0, i).concat(blobs.slice(i + 1)); } } + // XXXX This needs some attention before moving further. + // Code is not sufficiently transparent. if (!blobs.length) { return false; } else if (single && blobs.length === 2) { + // Clone to avoid corruption of style by affix migration during output if (single) { single = new CSL.Blob(single.blobs,single); } @@ -9047,15 +12194,18 @@ CSL.NameOutput.prototype._join = function (blobs, delimiter, single, multiple) { } else { delimiter_offset = 1; } + // It kind of makes sense down to here. for (i = 0, ilen = blobs.length - delimiter_offset; i < ilen; i += 1) { blobs[i].strings.suffix += delimiter; } if (blobs.length > 1) { var blob = blobs.pop(); if (multiple) { + // Clone to avoid corruption of style by affix migration during output multiple = new CSL.Blob(multiple.blobs,multiple); blobs.push(multiple); } else { + // Clone to avoid corruption of style by affix migration during output if (single) { single = new CSL.Blob(single.blobs,single); } @@ -9064,7 +12214,13 @@ CSL.NameOutput.prototype._join = function (blobs, delimiter, single, multiple) { blobs.push(blob); } } + + //this.state.output.openLevel(this._getToken(tokenname)); this.state.output.openLevel(); + + //this.state.output.openLevel(this._getToken("empty")); + // Delimiter is applied from separately saved source in this case, + // for discriminate application of single and multiple joins. if (single && multiple) { this.state.output.current.value().strings.delimiter = ""; } @@ -9074,14 +12230,23 @@ CSL.NameOutput.prototype._join = function (blobs, delimiter, single, multiple) { this.state.output.closeLevel(); return this.state.output.pop(); }; + + CSL.NameOutput.prototype._getToken = function (tokenname) { var token = this[tokenname]; if (tokenname === "institution") { var newtoken = new CSL.Token(); + // Which, hmm, is the same thing as "empty" + // Oh, well. + //newtoken.strings.prefix = token.prefix; + //newtoken.strings.suffix = token.suffix; return newtoken; } return token; }; + +/*global CSL: true */ + CSL.NameOutput.prototype.checkCommonAuthor = function(requireMatch) { if (!requireMatch) { return false; @@ -9112,13 +12277,19 @@ CSL.NameOutput.prototype.checkCommonAuthor = function(requireMatch) { this.state.tmp.done_vars.push(this.variables[0]); this.state.tmp.done_vars.push(this.variables[1]); } + // This may be counter-intuitive. + // This check controls whether we will fail on the this attempt at rendering + // and proceed with substitution. If the names match exactly (true), then + // we do *not* want to abort and continue with substitution. return !perfectMatch; }; + CSL.NameOutput.prototype.setCommonTerm = function () { var variables = this.variables; var varnames = variables.slice(); varnames.sort(); this.common_term = varnames.join(""); + // When no varnames are on offer if (!this.common_term) { return; } @@ -9130,6 +12301,8 @@ CSL.NameOutput.prototype.setCommonTerm = function () { has_term = this.state.getTerm(this.common_term, this.label[this.variables[0]].after.strings.form, 0); } } + + // When there is no common term if (!this.state.locale[this.state.opt.lang].terms[this.common_term] || !has_term || this.variables.length < 2) { @@ -9161,6 +12334,7 @@ CSL.NameOutput.prototype.setCommonTerm = function () { } } }; + CSL.NameOutput.prototype._compareNamesets = function (base_nameset, nameset) { if (!base_nameset || !nameset || base_nameset.length !== nameset.length) { return false; @@ -9175,24 +12349,35 @@ CSL.NameOutput.prototype._compareNamesets = function (base_nameset, nameset) { } return true; }; + +/*global CSL: true */ + CSL.NameOutput.prototype.constrainNames = function () { + // figure out how many names to include, in light of the disambig params + // this.names_count = 0; + //var pos = 0; var pos; for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) { var v = this.variables[i]; pos = this.nameset_base + i; + // Constrain independent authors here if (this.freeters[v].length) { this.state.tmp.names_max.push(this.freeters[v].length, "literal"); this._imposeNameConstraints(this.freeters, this.freeters_count, v, pos); this.names_count += this.freeters[v].length; } + + // Constrain institutions here if (this.institutions[v].length) { this.state.tmp.names_max.push(this.institutions[v].length, "literal"); this._imposeNameConstraints(this.institutions, this.institutions_count, v, pos); this.persons[v] = this.persons[v].slice(0, this.institutions[v].length); this.names_count += this.institutions[v].length; } + for (var j = 0, jlen = this.persons[v].length; j < jlen; j += 1) { + // Constrain affiliated authors here if (this.persons[v][j].length) { this.state.tmp.names_max.push(this.persons[v][j].length, "literal"); this._imposeNameConstraints(this.persons[v], this.persons_count[v], j, pos); @@ -9201,11 +12386,19 @@ CSL.NameOutput.prototype.constrainNames = function () { } } }; + CSL.NameOutput.prototype._imposeNameConstraints = function (lst, count, key, pos) { + // display_names starts as the original length of this list of names. var display_names = lst[key]; var discretionary_names_length = this.state.tmp["et-al-min"]; + + // Mappings, to allow existing disambiguation machinery to + // remain untouched. if (this.state.tmp.suppress_decorations) { if (this.state.tmp.disambig_request && this.state.tmp.disambig_request.names[pos]) { + // Oh. Trouble. + // state.tmp.nameset_counter is the number of the nameset + // in the disambiguation try-sequence. Ouch. discretionary_names_length = this.state.tmp.disambig_request.names[pos]; } else if (count[key] >= this.etal_min) { discretionary_names_length = this.etal_use_first; @@ -9213,23 +12406,39 @@ CSL.NameOutput.prototype._imposeNameConstraints = function (lst, count, key, pos } else { if (this.state.tmp.disambig_request && this.state.tmp.disambig_request.names[pos] > this.etal_use_first) { + if (count[key] < this.etal_min) { discretionary_names_length = count[key]; } else { discretionary_names_length = this.state.tmp.disambig_request.names[pos]; } } else if (count[key] >= this.etal_min) { + //discretionary_names_length = this.state.tmp["et-al-use-first"]; discretionary_names_length = this.etal_use_first; } + // XXXX: This is a workaround. Under some conditions. + // Where namesets disambiguate on one of the two names + // dropped here, it is possible for more than one + // in-text citation to be close (and indistinguishable) + // matches to a single bibliography entry. + // + // if (this.etal_use_last && discretionary_names_length > (this.etal_min - 2)) { discretionary_names_length = this.etal_min - 2; } } var sane = this.etal_min >= this.etal_use_first; var overlength = count[key] > discretionary_names_length; + // This var is used to control contextual join, and + // lies about the number of names when forceEtAl is true, + // unless normalized. if (discretionary_names_length > count[key]) { + + // Use actual truncated list length, to avoid overrun. discretionary_names_length = display_names.length; } + // forceEtAl is relevant when the author list is + // truncated to eliminate clutter. if (sane && overlength) { if (this.etal_use_last) { lst[key] = display_names.slice(0, discretionary_names_length).concat(display_names.slice(-1)); @@ -9239,7 +12448,19 @@ CSL.NameOutput.prototype._imposeNameConstraints = function (lst, count, key, pos } this.state.tmp.disambig_settings.names[pos] = lst[key].length; this.state.disambiguate.padBase(this.state.tmp.disambig_settings); + + + // ??? + //if (!this.state.tmp.disambig_request) { + // this.state.tmp.disambig_settings.givens[pos] = []; + //} }; + +// Disambiguate names (the number of names is controlled externally, by successive +// runs of the processor). + +/*global CSL: true */ + CSL.NameOutput.prototype.disambigNames = function () { var pos; for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) { @@ -9248,6 +12469,7 @@ CSL.NameOutput.prototype.disambigNames = function () { if (this.freeters[v].length) { this._runDisambigNames(this.freeters[v], pos); } + // Is this even necessary??? if (this.institutions[v].length) { if ("undefined" === typeof this.state.tmp.disambig_settings.givens[pos]) { this.state.tmp.disambig_settings.givens[pos] = []; @@ -9265,16 +12487,27 @@ CSL.NameOutput.prototype.disambigNames = function () { } } }; + CSL.NameOutput.prototype._runDisambigNames = function (lst, pos) { var chk, myform, myinitials, param, i, ilen, paramx; + //if (this.state.tmp.root === "bibliography") { + // return; + //} for (i = 0, ilen = lst.length; i < ilen; i += 1) { + // + // register the name in the global names disambiguation + // registry + if (!lst[i].given && !lst[i].family) { continue; } + myinitials = this.state.inheritOpt(this.name, "initialize-with"); this.state.registry.namereg.addname("" + this.Item.id, lst[i], i); chk = this.state.tmp.disambig_settings.givens[pos]; if ("undefined" === typeof chk) { + // Holes can appear in the list, probably due to institutional + // names that this doesn't touch. Maybe. This fills them up. for (var j = 0, jlen = pos + 1; j < jlen; j += 1) { if (!this.state.tmp.disambig_settings.givens[j]) { this.state.tmp.disambig_settings.givens[j] = []; @@ -9282,15 +12515,28 @@ CSL.NameOutput.prototype._runDisambigNames = function (lst, pos) { } } chk = this.state.tmp.disambig_settings.givens[pos][i]; + //if ("undefined" !== typeof chk && this.state.tmp.root === 'citation') { + //this.state.tmp.disambig_settings.givens[pos] = []; + //chk = undefined; + //} if ("undefined" === typeof chk) { myform = this.state.inheritOpt(this.name, "form", "name-form", "long"); param = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials); this.state.tmp.disambig_settings.givens[pos].push(param); } + // + // set the display mode default for givennames if required myform = this.state.inheritOpt(this.name, "form", "name-form", "long"); paramx = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials); + // this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials); if (this.state.tmp.disambig_request) { + // + // fix a request for initials that makes no sense. + // can't do this in disambig, because the availability + // of initials is not a global parameter. var val = this.state.tmp.disambig_settings.givens[pos][i]; + // This is limited to by-cite disambiguation. + // 2012-09-13: added lst[i].given check to condition if (val === 1 && this.state.citation.opt["givenname-disambiguation-rule"] === "by-cite" && ("undefined" === typeof this.state.inheritOpt(this.name, "initialize-with") @@ -9298,12 +12544,22 @@ CSL.NameOutput.prototype._runDisambigNames = function (lst, pos) { val = 2; } param = val; + // 2012-09-13: lst[i].given check protects against personal names + // that have no first name element. These were causing an infinite loop, + // this prevents that. if (this.state.opt["disambiguate-add-givenname"] && lst[i].given) { param = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, param, this.state.inheritOpt(this.name, "form", "name-form", "long"), this.state.inheritOpt(this.name, "initialize-with")); } } else { + // + // it clicks. here is where we will put the + // call to the names register, to get the floor value + // for an individual name. + // param = paramx; } + // Need to save off the settings based on subsequent + // form, when first cites are rendered. if (!this.state.tmp.just_looking && this.item && this.item.position === CSL.POSITION_FIRST) { if (paramx > param) { param = paramx; @@ -9314,57 +12570,83 @@ CSL.NameOutput.prototype._runDisambigNames = function (lst, pos) { if ("string" === typeof myinitials && ("undefined" === typeof this.name.strings["initialize"] || true === this.name.strings["initialize"])) { + this.state.tmp.disambig_settings.use_initials = true; } } } + //this.state.registry.registry[this.Item.id].disambig.givens = this.state.tmp.disambig_settings.givens.slice(); }; + +/*global CSL: true */ + CSL.NameOutput.prototype.getEtAlConfig = function () { var item = this.item; this["et-al"] = {}; + this.state.output.append(this.etal_term, this.etal_style, true); this["et-al"].single = this.state.output.pop(); this["et-al"].single.strings.suffix = this.etal_suffix; this["et-al"].single.strings.prefix = this.etal_prefix_single; + this.state.output.append(this.etal_term, this.etal_style, true); this["et-al"].multiple = this.state.output.pop(); this["et-al"].multiple.strings.suffix = this.etal_suffix; this["et-al"].multiple.strings.prefix = this.etal_prefix_multiple; + + // Et-al style parameters (may be sidestepped by disambiguation + // in util_names_constraints.js) if ("undefined" === typeof item) { item = {}; } + //print("== getEtAlConfig() == "+this.state.tmp.area); + if (item.position) { if (this.state.inheritOpt(this.name, "et-al-subsequent-min")) { + // XX this.etal_min = this.state.inheritOpt(this.name, "et-al-subsequent-min"); } else { + // XX this.etal_min = this.state.inheritOpt(this.name, "et-al-min"); } if (this.state.inheritOpt(this.name, "et-al-subsequent-use-first")) { + // XX this.etal_use_first = this.state.inheritOpt(this.name, "et-al-subsequent-use-first"); } else { + // XX this.etal_use_first = this.state.inheritOpt(this.name, "et-al-use-first"); } } else { if (this.state.tmp["et-al-min"]) { this.etal_min = this.state.tmp["et-al-min"]; } else { + // XX this.etal_min = this.state.inheritOpt(this.name, "et-al-min"); } if (this.state.tmp["et-al-use-first"]) { this.etal_use_first = this.state.tmp["et-al-use-first"]; } else { + // XX this.etal_use_first = this.state.inheritOpt(this.name, "et-al-use-first"); } if ("boolean" === typeof this.state.tmp["et-al-use-last"]) { + //print(" etal_use_last from tmp: "+this.state.tmp["et-al-use-last"]); this.etal_use_last = this.state.tmp["et-al-use-last"]; } else { + //print(" etal_use_last from name: "+this.name.strings["et-al-use-last"]); + // XX this.etal_use_last = this.state.inheritOpt(this.name, "et-al-use-last"); } + //print(" etal_use_last: "+this.etal_use_last); } + // Provided for use as the starting level for disambiguation. if (!this.state.tmp["et-al-min"]) { this.state.tmp["et-al-min"] = this.etal_min; } }; + +/*global CSL: true */ + CSL.NameOutput.prototype.setEtAlParameters = function () { var i, ilen, j, jlen; for (i = 0, ilen = this.variables.length; i < ilen; i += 1) { @@ -9387,6 +12669,7 @@ CSL.NameOutput.prototype.setEtAlParameters = function () { } } }; + CSL.NameOutput.prototype._setEtAlParameter = function (type, v, j) { var lst, count; if (type === "persons") { @@ -9418,15 +12701,22 @@ CSL.NameOutput.prototype._setEtAlParameter = function (type, v, j) { } } }; + +/*global CSL: true */ + CSL.NameOutput.prototype.renderAllNames = function () { + // Note that et-al/ellipsis parameters are set on the basis + // of rendering order through the whole cite. var pos; for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) { var v = this.variables[i]; + if (this.freeters[v].length || this.institutions[v].length) { if (!this.state.tmp.group_context.tip.condition) { this.state.tmp.just_did_number = false; } } + pos = this.nameset_base + i; if (this.freeters[v].length) { this.freeters[v] = this._renderNames(v, this.freeters[v], pos); @@ -9437,20 +12727,32 @@ CSL.NameOutput.prototype.renderAllNames = function () { } this.renderInstitutionNames(); }; + CSL.NameOutput.prototype.renderInstitutionNames = function () { + // Institutions are split to string list as + // this.institutions[v]["long"] and this.institutions[v]["short"] for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) { var v = this.variables[i]; for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) { var institution; + var name = this.institutions[v][j]; + + + + // XXX Start here for institutions + // Figure out the three segments: primary, secondary, tertiary var j, jlen, localesets; if (this.state.tmp.extension) { localesets = ["sort"]; } else if (name.isInstitution || name.literal) { + // Will never hit this in this function, but preserving + // in case we factor this out. localesets = this.state.opt['cite-lang-prefs'].institutions; } else { localesets = this.state.opt['cite-lang-prefs'].persons; } + var slot = {primary:'locale-orig',secondary:false,tertiary:false}; if (localesets) { var slotnames = ["primary", "secondary", "tertiary"]; @@ -9469,21 +12771,29 @@ CSL.NameOutput.prototype.renderInstitutionNames = function () { && !(this.state.tmp.area === "citation" && this.state.opt.xclass === "note" && this.item && !this.item.position)) { + slot.secondary = false; slot.tertiary = false; } + // Get normalized name object for a start. + // true invokes fallback this.setRenderedName(name); + + // XXXX FROM HERE (instututions) var institution = this._renderInstitutionName(v, name, slot, j); + //this.institutions[v][j] = this._join(institution, ""); this.institutions[v][j] = institution; } } }; + CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { var secondary, tertiary, long_style, short_style, institution, institution_short, institution_long; var res = this.getName(name, slot.primary, true); var primary = res.name; var usedOrig = res.usedOrig; if (primary) { + //print("primary, v, j = "+primary+", "+v+", "+j); primary = this.fixupInstitution(primary, v, j); } secondary = false; @@ -9495,6 +12805,7 @@ CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { secondary = this.fixupInstitution(secondary, v, j); } } + //Zotero.debug("XXX [2] secondary: "+secondary["long"].literal+", slot.secondary: "+slot.secondary); tertiary = false; if (slot.tertiary) { res = this.getName(name, slot.tertiary, false, usedOrig); @@ -9529,10 +12840,12 @@ CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { } switch (this.institution.strings["institution-parts"]) { case "short": + // No multilingual for pure short form institution names. if (primary["short"].length) { short_style = this._getShortStyle(); institution = [this._composeOneInstitutionPart([n.s.pri, n.s.sec, n.s.ter], slot, short_style, v)]; } else { + // Fail over to long. long_style = this._getLongStyle(primary, v, j); institution = [this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v)]; } @@ -9541,6 +12854,7 @@ CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { long_style = this._getLongStyle(primary, v, j); short_style = this._getShortStyle(); institution_short = this._renderOneInstitutionPart(primary["short"], short_style); + // true is to include multilingual supplement institution_long = this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v); institution = [institution_short, institution_long]; break; @@ -9548,11 +12862,13 @@ CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { long_style = this._getLongStyle(primary, v, j); short_style = this._getShortStyle(); institution_short = this._renderOneInstitutionPart(primary["short"], short_style); + // true is to include multilingual supplement institution_long = this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v); institution = [institution_long, institution_short]; break; default: long_style = this._getLongStyle(primary, v, j); + // true is to include multilingual supplement institution = [this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v)]; break; } @@ -9560,6 +12876,7 @@ CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) { this.state.tmp.name_node.children.push(blob); return blob; }; + CSL.NameOutput.prototype._composeOneInstitutionPart = function (names, slot, style) { var primary = false, secondary = false, tertiary = false, primary_tok, secondary_tok, tertiary_tok; if (names[0]) { @@ -9586,14 +12903,18 @@ CSL.NameOutput.prototype._composeOneInstitutionPart = function (names, slot, sty if (names[2]) { tertiary = this._renderOneInstitutionPart(names[2], style); } + // Compose var institutionblob; if (secondary || tertiary) { this.state.output.openLevel("empty"); + this.state.output.append(primary); + secondary_tok = CSL.Util.cloneToken(style); if (slot.secondary) { secondary_tok.strings.prefix = this.state.opt.citeAffixes.institutions[slot.secondary].prefix; secondary_tok.strings.suffix = this.state.opt.citeAffixes.institutions[slot.secondary].suffix; + // Add a space if empty if (!secondary_tok.strings.prefix) { secondary_tok.strings.prefix = " "; } @@ -9604,10 +12925,12 @@ CSL.NameOutput.prototype._composeOneInstitutionPart = function (names, slot, sty this.state.output.openLevel(secondary_outer); this.state.output.append(secondary, secondary_tok); this.state.output.closeLevel(); + tertiary_tok = CSL.Util.cloneToken(style); if (slot.tertiary) { tertiary_tok.strings.prefix = this.state.opt.citeAffixes.institutions[slot.tertiary].prefix; tertiary_tok.strings.suffix = this.state.opt.citeAffixes.institutions[slot.tertiary].suffix; + // Add a space if empty if (!tertiary_tok.strings.prefix) { tertiary_tok.strings.prefix = " "; } @@ -9618,17 +12941,23 @@ CSL.NameOutput.prototype._composeOneInstitutionPart = function (names, slot, sty this.state.output.openLevel(tertiary_outer); this.state.output.append(tertiary, tertiary_tok); this.state.output.closeLevel(); + this.state.output.closeLevel(); + institutionblob = this.state.output.pop(); } else { institutionblob = primary; } return institutionblob; }; + CSL.NameOutput.prototype._renderOneInstitutionPart = function (blobs, style) { for (var i = 0, ilen = blobs.length; i < ilen; i += 1) { if (blobs[i]) { var str = blobs[i]; + // XXXXX Cut-and-paste code in multiple locations. This code block should be + // collected in a function. + // Tag: strip-periods-block if (this.state.tmp.strip_periods) { str = str.replace(/\./g, ""); } else { @@ -9639,6 +12968,7 @@ CSL.NameOutput.prototype._renderOneInstitutionPart = function (blobs, style) { } } } + //this.state.output.append(blobs[i], style, true); this.state.tmp.group_context.tip.variable_success = true; this.state.tmp.can_substitute.replace(false, CSL.LITERAL); if (str === "!here>>>") { @@ -9654,16 +12984,24 @@ CSL.NameOutput.prototype._renderOneInstitutionPart = function (blobs, style) { } return this._join(blobs, this.institution.strings["part-separator"]); }; + CSL.NameOutput.prototype._renderNames = function (v, values, pos, j) { + // var ret = false; if (values.length) { var names = []; for (var i = 0, ilen = values.length; i < ilen; i += 1) { var name = values[i]; + + // XXX We'll start here with attempts. + // Figure out the three segments: primary, secondary, tertiary var ret, localesets; + if (this.state.tmp.extension) { localesets = ["sort"]; } else if (name.isInstitution || name.literal) { + // Will never hit this in this function, but preserving + // in case we factor this out. localesets = this.state.opt['cite-lang-prefs'].institutions; } else { localesets = this.state.opt['cite-lang-prefs'].persons; @@ -9684,10 +13022,15 @@ CSL.NameOutput.prototype._renderNames = function (v, values, pos, j) { && !(this.state.tmp.area === "citation" && this.state.opt.xclass === "note" && this.item && !this.item.position))) { + slot.secondary = false; slot.tertiary = false; } + + // primary + // true is for fallback this.setRenderedName(name); + if (!name.literal && !name.isInstitution) { var nameBlob = this._renderPersonalName(v, name, slot, pos, i, j); var nameToken = CSL.Util.cloneToken(this.name); @@ -9697,11 +13040,16 @@ CSL.NameOutput.prototype._renderNames = function (v, values, pos, j) { names.push(this._renderInstitutionName(v, name, slot, j)); } } + //ret = this._join(names, ""); ret = this.joinPersons(names, pos, j); } return ret; }; + + CSL.NameOutput.prototype._renderPersonalName = function (v, name, slot, pos, i, j) { + // XXXX FROM HERE (persons) + var res = this.getName(name, slot.primary, true); var primary = this._renderOnePersonalName(res.name, pos, i, j); var secondary = false; @@ -9718,36 +13066,49 @@ CSL.NameOutput.prototype._renderPersonalName = function (v, name, slot, pos, i, tertiary = this._renderOnePersonalName(res.name, pos, i, j); } } + // Now compose them to a unit var personblob; if (secondary || tertiary) { + this.state.output.openLevel("empty"); + this.state.output.append(primary); + var secondary_tok = new CSL.Token(); if (slot.secondary) { secondary_tok.strings.prefix = this.state.opt.citeAffixes.persons[slot.secondary].prefix; secondary_tok.strings.suffix = this.state.opt.citeAffixes.persons[slot.secondary].suffix; + // Add a space if empty if (!secondary_tok.strings.prefix) { secondary_tok.strings.prefix = " "; } } this.state.output.append(secondary, secondary_tok); + var tertiary_tok = new CSL.Token(); if (slot.tertiary) { tertiary_tok.strings.prefix = this.state.opt.citeAffixes.persons[slot.tertiary].prefix; tertiary_tok.strings.suffix = this.state.opt.citeAffixes.persons[slot.tertiary].suffix; + // Add a space if empty if (!tertiary_tok.strings.prefix) { tertiary_tok.strings.prefix = " "; } } this.state.output.append(tertiary, tertiary_tok); + this.state.output.closeLevel(); + personblob = this.state.output.pop(); } else { personblob = primary; } return personblob; }; + CSL.NameOutput.prototype._isRomanesque = function (name) { + // 0 = entirely non-romanesque + // 1 = mixed content + // 2 = pure romanesque var ret = 2; if (!name.family.replace(/\"/g, '').match(CSL.ROMANESQUE_REGEXP)) { ret = 0; @@ -9766,8 +13127,10 @@ CSL.NameOutput.prototype._isRomanesque = function (name) { ret = 1; } } + //print("name: "+name.given+", multi: "+name.multi+", ret: "+ret); return ret; }; + CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { var name = value; var dropping_particle = this._droppingParticle(name, pos, j); @@ -9804,14 +13167,17 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { } } var has_hyphenated_non_dropping_particle = hasJoiningPunctuation(non_dropping_particle); + var blob, merged, first, second; if (romanesque === 0) { + // XXX handle affixes for given and family blob = this._join([non_dropping_particle, family, given], ""); } else if (romanesque === 1 || name["static-ordering"]) { // entry likes sort order blob = this._join([non_dropping_particle, family, given], " "); } else if (name["reverse-ordering"]) { // entry likes reverse order blob = this._join([given, non_dropping_particle, family], " "); } else if (this.state.tmp.sort_key_flag) { + // ok with no affixes here if (this.state.opt["demote-non-dropping-particle"] === "never") { first = this._join([non_dropping_particle, family, dropping_particle], " "); merged = this._join([first, given], this.state.opt.sort_sep); @@ -9822,11 +13188,23 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { blob = this._join([merged, suffix], " "); } } else if (this.state.inheritOpt(this.name, "name-as-sort-order") === "all" || (this.state.inheritOpt(this.name, "name-as-sort-order") === "first" && i === 0 && (j === 0 || "undefined" === typeof j))) { + // + // Discretionary sort ordering and inversions + // if (["Lord", "Lady"].indexOf(name.given) > -1) { sort_sep = ", "; } + + // XXX Needs a more robust solution than this + // XXX See https://forums.zotero.org/discussion/30974/any-idea-why-an-a-author-comes-last-in-the-bibliography/#Item_30 + + //if (["always", "display-and-sort"].indexOf(this.state.opt["demote-non-dropping-particle"]) > -1 && !has_hyphenated_non_dropping_particle) { if (["always", "display-and-sort"].indexOf(this.state.opt["demote-non-dropping-particle"]) > -1) { + // Drop non-dropping particle + //second = this._join([given, dropping_particle, non_dropping_particle], " "); second = this._join([given, dropping_particle], (name["comma-dropping-particle"] + " ")); + + // This would be a problem with al-Ghazali. Avoided by has_hyphenated_non_dropping_particle check above. second = this._join([second, non_dropping_particle], " "); if (second && this.given) { second.strings.prefix = this.given.strings.prefix; @@ -9839,6 +13217,13 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { merged = this._join([family, second], sort_sep); blob = this._join([merged, suffix], sort_sep); } else { + // Don't drop particle. + // Don't do this + //if (this.state.tmp.area === "bibliography" && !this.state.tmp.term_predecessor && non_dropping_particle) { + // if (!has_hyphenated_non_dropping_particle) { + // non_dropping_particle.blobs = CSL.Output.Formatters["capitalize-first"](this.state, non_dropping_particle.blobs) + // } + //} if (has_hyphenated_non_dropping_particle) { first = this._join([non_dropping_particle, family], ""); } else { @@ -9848,27 +13233,34 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { first.strings.prefix = this.family.strings.prefix; first.strings.suffix = this.family.strings.suffix; } + second = this._join([given, dropping_particle], (name["comma-dropping-particle"] + " ")); + //second = this._join([given, dropping_particle], " "); if (second && 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) { + var dp = name["dropping-particle"]; + var apostrophes = ["'","\u02bc","\u2019","-"]; + if (apostrophes.indexOf(dp.slice(-1)) > -1 && dp.slice(0, -1) !== "de") { family = this._join([dropping_particle, family], ""); dropping_particle = false; } } + var space = " "; if (this.state.inheritOpt(this.name, "initialize-with") && this.state.inheritOpt(this.name, "initialize-with").match(/[\u00a0\ufeff]/) && ["fr", "ru", "cs"].indexOf(this.state.opt["default-locale"][0].slice(0, 2)) > -1) { space = "\u00a0"; } + if (has_hyphenated_non_dropping_particle) { second = this._join([non_dropping_particle, family], ""); second = this._join([dropping_particle, second], space); @@ -9889,12 +13281,25 @@ CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) { } blob = this._join([given, second], (name["comma-dropping-particle"] + space)); } + // XXX Just generally assume for the present that personal names render something this.state.tmp.group_context.tip.variable_success = true; this.state.tmp.can_substitute.replace(false, CSL.LITERAL); this.state.tmp.term_predecessor = true; + // notSerious + //this.state.output.append(blob, "literal", true); + //var ret = this.state.output.pop(); this.state.tmp.name_node.children.push(blob); return blob; }; + +/* + // Do not include given name, dropping particle or suffix in strict short form of name + + // initialize if appropriate +*/ + +// Input names should be touched by _normalizeNameInput() +// exactly once: this is not idempotent. CSL.NameOutput.prototype._normalizeNameInput = function (value) { var name = { literal:value.literal, @@ -9917,6 +13322,9 @@ CSL.NameOutput.prototype._normalizeNameInput = function (value) { this._parseName(name); return name; }; + +// _transformNameset() replaced with enhanced transform.name(). + CSL.NameOutput.prototype._stripPeriods = function (tokname, str) { var decor_tok = this[tokname + "_decor"]; if (str) { @@ -9933,6 +13341,7 @@ CSL.NameOutput.prototype._stripPeriods = function (tokname, str) { } return str; }; + CSL.NameOutput.prototype._nonDroppingParticle = function (name) { var ndp = name["non-dropping-particle"]; if (ndp && this.state.tmp.sort_key_flag) { @@ -9944,6 +13353,7 @@ CSL.NameOutput.prototype._nonDroppingParticle = function (name) { } return false; }; + CSL.NameOutput.prototype._droppingParticle = function (name, pos, j) { var dp = name["dropping-particle"]; if (dp && this.state.tmp.sort_key_flag) { @@ -9970,6 +13380,7 @@ CSL.NameOutput.prototype._droppingParticle = function (name, pos, j) { } return false; }; + CSL.NameOutput.prototype._familyName = function (name) { var str = this._stripPeriods("family", name.family); if (this.state.output.append(str, this.family_decor, true)) { @@ -9977,8 +13388,17 @@ CSL.NameOutput.prototype._familyName = function (name) { } return false; }; + CSL.NameOutput.prototype._givenName = function (name, pos, i) { var ret; + // citation + // use disambig as-is + // biblography + // use disambig only if it boosts over the default + // SO WHAT IS THE DEFAULT? + // A: If "form" is short, it's 0. + // If "form" is long, initialize-with exists (and initialize is not false) it's 1 + // If "form" is long, and initialize_with does not exist, it's 2. var formIsShort = this.state.inheritOpt(this.name, "form", "name-form", "long") !== "long"; var initializeIsTurnedOn = this.state.inheritOpt(this.name, "initialize") !== false; var hasInitializeWith = "string" === typeof this.state.inheritOpt(this.name, "initialize-with") && !name.block_initialize; @@ -10017,6 +13437,7 @@ CSL.NameOutput.prototype._givenName = function (name, pos, i) { } else if (useLevel === 2) { name.given = CSL.Util.Names.unInitialize(this.state, name.given); } + var str = this._stripPeriods("given", name.given); var rendered = this.state.output.append(str, this.given_decor, true); if (rendered) { @@ -10025,11 +13446,15 @@ CSL.NameOutput.prototype._givenName = function (name, pos, i) { } return false; }; + CSL.NameOutput.prototype._nameSuffix = function (name) { + var str = name.suffix, ret; + if ("string" === typeof this.state.inheritOpt(this.name, "initialize-with")) { str = CSL.Util.Names.initializeWith(this.state, name.suffix, this.state.inheritOpt(this.name, "initialize-with"), true); } + str = this._stripPeriods("family", str); var toSuffix = ''; if (str && str.slice(-1) === '.') { @@ -10044,6 +13469,7 @@ CSL.NameOutput.prototype._nameSuffix = function (name) { } return false; }; + CSL.NameOutput.prototype._getLongStyle = function (name) { var long_style; if (name["short"].length) { @@ -10060,6 +13486,7 @@ CSL.NameOutput.prototype._getLongStyle = function (name) { } return long_style; }; + CSL.NameOutput.prototype._getShortStyle = function () { var short_style; if (this.institutionpart["short"]) { @@ -10069,6 +13496,7 @@ CSL.NameOutput.prototype._getShortStyle = function () { } return short_style; }; + CSL.NameOutput.prototype._parseName = function (name) { if (!name["parse-names"] && "undefined" !== typeof name["parse-names"]) { return name; @@ -10082,6 +13510,7 @@ 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; @@ -10096,18 +13525,42 @@ CSL.NameOutput.prototype._parseName = function (name) { } } }; + +/* + * Return a single name object + */ + +// The interface is a mess, but this should serve. + CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stopOrig) { + + // Needs to tell us whether we used orig or not. + if (stopOrig && slotLocaleset === 'locale-orig') { return {name:false,usedOrig:stopOrig}; } + + // Normalize to string if (!name.family) { name.family = ""; } if (!name.given) { name.given = ""; } + + // Recognized params are: + // block-initialize + // transliterated + // static-ordering + // full-form-always + // All default to false, except for static-ordering, which is initialized + // with a sniff. var name_params = {}; + // Determines the default static-order setting based on the characters + // used in the headline field. Will be overridden by locale-based + // parameters evaluated against explicit lang tags set on the (sub)field. name_params["static-ordering"] = this.getStaticOrder(name); + var foundTag = true; var langTag; if (slotLocaleset !== 'locale-orig') { @@ -10121,6 +13574,7 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop var isInstitution = name.isInstitution; name = name.multi._key[langTag]; name.isInstitution = isInstitution; + // Set name formatting params name_params = this.getNameParams(langTag); name_params.transliterated = true; break; @@ -10128,6 +13582,7 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop } } } + if (!foundTag) { langTag = false; if (name.multi && name.multi.main) { @@ -10139,9 +13594,12 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop name_params = this.getNameParams(langTag); } } + if (!fallback && !foundTag) { return {name:false,usedOrig:stopOrig}; } + + // Normalize to string (again) if (!name.family) { name.family = ""; } @@ -10152,6 +13610,7 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop delete name.family; delete name.given; } + // var clone the item before writing into it name = { family:name.family, given:name.given, @@ -10171,6 +13630,7 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop isInstitution:name.isInstitution, multi:name.multi }; + if (!name.literal && (!name.given && name.family && name.isInstitution)) { name.literal = name.family; } @@ -10187,6 +13647,7 @@ CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stop } return {name:name,usedOrig:usedOrig}; }; + CSL.NameOutput.prototype.getNameParams = function (langTag) { var ret = {}; var langspec = CSL.localeResolve(this.Item.language, this.state.opt["default-locale"][0]); @@ -10206,11 +13667,13 @@ CSL.NameOutput.prototype.getNameParams = function (langTag) { if (name_never_short && name_never_short[field_lang_bare]) { ret["full-form-always"] = true; } + if (ret["static-ordering"]) { ret["block-initialize"] = true; } return ret; }; + CSL.NameOutput.prototype.setRenderedName = function (name) { if (this.state.tmp.area === "bibliography") { var strname = ""; @@ -10222,27 +13685,24 @@ CSL.NameOutput.prototype.setRenderedName = function (name) { this.state.tmp.rendered_name.push(strname); } }; + CSL.NameOutput.prototype.fixupInstitution = function (name, varname, listpos) { - if (this.state.sys.getHumanForm && "legal_case" === this.Item.type && "authority" === varname) { - name.literal = this.state.sys.getHumanForm(this.Item.jurisdiction, name.literal, true); - } name = this._splitInstitution(name, varname, listpos); + // XXX This should be embedded in the institution name function. if (this.institution.strings["reverse-order"]) { name["long"].reverse(); } + var long_form = name["long"]; var short_form = name["long"].slice(); var use_short_form = false; if (this.state.sys.getAbbreviation) { var jurisdiction = this.Item.jurisdiction; for (var j = 0, jlen = long_form.length; j < jlen; j += 1) { - var normalizedKey = long_form[j]; - if (this.state.sys.normalizeAbbrevsKey) { - normalizedKey = this.state.sys.normalizeAbbrevsKey(varname, long_form[j]); - } - jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-part", normalizedKey); - if (this.state.transform.abbrevs[jurisdiction]["institution-part"][normalizedKey]) { - short_form[j] = this.state.transform.abbrevs[jurisdiction]["institution-part"][normalizedKey]; + var abbrevKey = long_form[j]; + jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-part", abbrevKey); + if (this.state.transform.abbrevs[jurisdiction]["institution-part"][abbrevKey]) { + short_form[j] = this.state.transform.abbrevs[jurisdiction]["institution-part"][abbrevKey]; use_short_form = true; } } @@ -10254,6 +13714,8 @@ CSL.NameOutput.prototype.fixupInstitution = function (name, varname, listpos) { } return name; }; + + CSL.NameOutput.prototype.getStaticOrder = function (name, refresh) { var static_ordering_val = false; if (!refresh && name["static-ordering"]) { @@ -10268,31 +13730,40 @@ CSL.NameOutput.prototype.getStaticOrder = function (name, refresh) { if (this.state.opt['auto-vietnamese-names'] && (CSL.VIETNAMESE_NAMES.exec(name.family + " " + name.given) && CSL.VIETNAMESE_SPECIALS.exec(name.family + name.given))) { + static_ordering_val = true; } } return static_ordering_val; }; + + CSL.NameOutput.prototype._splitInstitution = function (value, v, i) { var ret = {}; + // Due to a bug in Juris-M, there are a small number of items in my accounts that have + // a institution parent, and a personal child. The bug that created them was fixed before + // release, but this hack keeps them from crashing the processor. if (!value.literal && value.family) { value.literal = value.family; delete value.family; } var splitInstitution = value.literal.replace(/\s*\|\s*/g, "|"); + // check for total and utter abbreviation IFF form="short" splitInstitution = splitInstitution.split("|"); if (this.institution.strings.form === "short" && this.state.sys.getAbbreviation) { + // On a match, drop unused elements to yield a single key. var jurisdiction = this.Item.jurisdiction; for (var j = splitInstitution.length; j > 0; j += -1) { var str = splitInstitution.slice(0, j).join("|"); - var normalizedKey = str; - if (this.state.sys.normalizeAbbrevsKey) { - normalizedKey = this.state.sys.normalizeAbbrevsKey(v, str); - } - jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-entire", normalizedKey); - if (this.state.transform.abbrevs[jurisdiction]["institution-entire"][normalizedKey]) { - var splitLst = this.state.transform.abbrevs[jurisdiction]["institution-entire"][normalizedKey]; + var abbrevKey = str; + jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-entire", abbrevKey); + if (this.state.transform.abbrevs[jurisdiction]["institution-entire"][abbrevKey]) { + var splitLst = this.state.transform.abbrevs[jurisdiction]["institution-entire"][abbrevKey]; + splitLst = this.state.transform.quashCheck(splitLst); + + // If the abbreviation has date cut-offs, find the most recent + // abbreviation within scope. var splitSplitLst = splitLst.split(/>>[0-9]{4}>>/); var m = splitLst.match(/>>([0-9]{4})>>/); splitLst = splitSplitLst.pop(); @@ -10311,10 +13782,13 @@ CSL.NameOutput.prototype._splitInstitution = function (value, v, i) { } } splitInstitution.reverse(); + //print("into _trimInstitution with splitInstitution, v, i = "+splitInstitution+", "+v+", "+i); ret["long"] = this._trimInstitution(splitInstitution, v, i); return ret; }; + CSL.NameOutput.prototype._trimInstitution = function (subunits, v) { + // var use_first = false; var append_last = false; var s = subunits.slice(); @@ -10324,6 +13798,7 @@ CSL.NameOutput.prototype._trimInstitution = function (subunits, v) { use_first = this.institution.strings["use-first"]; } if ("undefined" !== typeof this.institution.strings["stop-last"]) { + // stop-last is negative when present stop_last = this.institution.strings["stop-last"]; } else if ("authority" === v && this.state.tmp.authority_stop_last) { stop_last = this.state.tmp.authority_stop_last; @@ -10359,9 +13834,14 @@ CSL.NameOutput.prototype._trimInstitution = function (subunits, v) { append_last = 0; } } + // Now that we've determined the value of append_last + // (use-last), avoid overlaps. if (use_first > subunits.length - append_last) { use_first = subunits.length - append_last; } + + // This could be more clear. use-last takes priority + // in the event of overlap, because of adjustment above subunits = subunits.slice(0, use_first); s = s.slice(use_first); if (append_last) { @@ -10374,11 +13854,15 @@ CSL.NameOutput.prototype._trimInstitution = function (subunits, v) { } return subunits; }; + +/*global CSL: true */ + CSL.PublisherOutput = function (state, group_tok) { this.state = state; this.group_tok = group_tok; this.varlist = []; }; + CSL.PublisherOutput.prototype.render = function () { this.clearVars(); this.composeAndBlob(); @@ -10386,6 +13870,15 @@ CSL.PublisherOutput.prototype.render = function () { this.composePublishers(); this.joinPublishers(); }; + + +// XXX Figure out how to adapt this to the House of Lords / House of Commons +// joint committee case + +// name_delimiter +// delimiter_precedes_last +// and + CSL.PublisherOutput.prototype.composeAndBlob = function () { this.and_blob = {}; var and_term = false; @@ -10399,9 +13892,11 @@ CSL.PublisherOutput.prototype.composeAndBlob = function () { tok.strings.prefix = " "; this.state.output.append(and_term, tok, true); var no_delim = this.state.output.pop(); + tok.strings.prefix = this.group_tok.strings["subgroup-delimiter"]; this.state.output.append(and_term, tok, true); var with_delim = this.state.output.pop(); + this.and_blob.single = false; this.and_blob.multiple = false; if (and_term) { @@ -10416,17 +13911,22 @@ CSL.PublisherOutput.prototype.composeAndBlob = function () { } } }; + + CSL.PublisherOutput.prototype.composeElements = function () { for (var i = 0, ilen = 2; i < ilen; i += 1) { var varname = ["publisher", "publisher-place"][i]; for (var j = 0, jlen = this["publisher-list"].length; j < jlen; j += 1) { var str = this[varname + "-list"][j]; var tok = this[varname + "-token"]; + // notSerious this.state.output.append(str, tok, true); this[varname + "-list"][j] = this.state.output.pop(); } } }; + + CSL.PublisherOutput.prototype.composePublishers = function () { var blobs; for (var i = 0, ilen = this["publisher-list"].length; i < ilen; i += 1) { @@ -10434,13 +13934,21 @@ CSL.PublisherOutput.prototype.composePublishers = function () { this["publisher-list"][i] = this._join(blobs, this.group_tok.strings.delimiter); } }; + + CSL.PublisherOutput.prototype.joinPublishers = function () { var blobs = this["publisher-list"]; var publishers = this._join(blobs, this.group_tok.strings["subgroup-delimiter"], this.and_blob.single, this.and_blob.multiple, this.group_tok); this.state.output.append(publishers, "literal"); }; + + +// blobs, delimiter, single, multiple, tokenname +// Tokenname is a key at top level of this object. CSL.PublisherOutput.prototype._join = CSL.NameOutput.prototype._join; CSL.PublisherOutput.prototype._getToken = CSL.NameOutput.prototype._getToken; + + CSL.PublisherOutput.prototype.clearVars = function () { this.state.tmp["publisher-list"] = false; this.state.tmp["publisher-place-list"] = false; @@ -10448,6 +13956,9 @@ CSL.PublisherOutput.prototype.clearVars = function () { this.state.tmp["publisher-token"] = false; this.state.tmp["publisher-place-token"] = false; }; + +/*global CSL: true */ + CSL.evaluateLabel = function (node, state, Item, item) { var myterm; if ("locator" === node.strings.term) { @@ -10464,8 +13975,11 @@ CSL.evaluateLabel = function (node, state, Item, item) { } else { myterm = node.strings.term; } + + // Plurals detection. var plural = node.strings.plural; if ("number" !== typeof plural) { + // (node, ItemObject, variable, type) var theItem = (item && node.strings.term === "locator") ? item : Item; if (theItem[node.strings.term]) { state.processNumber(false, theItem, node.strings.term, Item.type); @@ -10476,6 +13990,7 @@ CSL.evaluateLabel = function (node, state, Item, item) { state.tmp.shadow_numbers[node.strings.term].labelCapitalizeIfFirst = node.strings.capitalize_if_first; state.tmp.shadow_numbers[node.strings.term].labelDecorations = node.decorations.slice(); } + if (["locator", "number", "page"].indexOf(node.strings.term) > -1 && state.tmp.shadow_numbers[node.strings.term].label) { myterm = state.tmp.shadow_numbers[node.strings.term].label; } @@ -10488,6 +14003,7 @@ CSL.evaluateLabel = function (node, state, Item, item) { } return CSL.castLabel(state, node, myterm, plural, CSL.TOLERANT); }; + CSL.castLabel = function (state, node, term, plural, mode) { var label_form = node.strings.form; var label_capitalize_if_first = node.strings.capitalize_if_first; @@ -10501,6 +14017,9 @@ CSL.castLabel = function (state, node, term, plural, mode) { if (label_capitalize_if_first) { ret = CSL.Output.Formatters["capitalize-first"](state, ret); } + // XXXXX Cut-and-paste code in multiple locations. This code block should be + // collected in a function. + // Tag: strip-periods-block if (state.tmp.strip_periods) { ret = ret.replace(/\./g, ""); } else { @@ -10513,6 +14032,9 @@ CSL.castLabel = function (state, node, term, plural, mode) { } return ret; }; + +/*global CSL: true */ + CSL.Node.name = { build: function (state, target) { var func; @@ -10524,19 +14046,37 @@ CSL.Node.name = { } else { oldTmpRoot = state.tmp.root; } + // Many CSL styles set et-al-[min|use-first] + // and et-al-subsequent-[min|use-first] to the same + // value. + // Set state.opt.update_mode = CSL.POSITION if + // et-al-subsequent-min or et-al-subsequent-use-first + // are set AND their value differs from their plain + // counterparts. if (state.inheritOpt(this, "et-al-subsequent-min") && (state.inheritOpt(this, "et-al-subsequent-min") !== state.inheritOpt(this, "et-al-min"))) { + state.opt.update_mode = CSL.POSITION; } if (state.inheritOpt(this, "et-al-subsequent-use-first") && (state.inheritOpt(this, "et-al-subsequent-use-first") !== state.inheritOpt(this, "et-al-use-first"))) { + state.opt.update_mode = CSL.POSITION; } + state.tmp.root = oldTmpRoot; + func = function (state) { + // Et-al (onward processing in node_etal.js and node_names.js) + // XXXXX Why is this necessary? This is available on this.name, right? state.tmp.etal_term = "et-al"; + + // Use default delimiter as fallback, in a way that allows explicit + // empty strings. state.tmp.name_delimiter = state.inheritOpt(this, "delimiter", "name-delimiter", ", "); state.tmp["delimiter-precedes-et-al"] = state.inheritOpt(this, "delimiter-precedes-et-al"); + + // And if ("text" === state.inheritOpt(this, "and")) { this.and_term = state.getTerm("and", "long", 0); } else if ("symbol" === state.inheritOpt(this, "and")) { @@ -10550,10 +14090,16 @@ CSL.Node.name = { if (CSL.STARTSWITH_ROMANESQUE_REGEXP.test(this.and_term)) { this.and_prefix_single = " "; this.and_prefix_multiple = ", "; + // Workaround to allow explicit empty string + // on cs:name delimiter. if ("string" === typeof state.tmp.name_delimiter) { this.and_prefix_multiple = state.tmp.name_delimiter; } this.and_suffix = " "; + + // Really can't inspect these values in the build phase. Sorry. + //state.build.name_delimiter = this.strings.delimiter; + } else { this.and_prefix_single = ""; this.and_prefix_multiple = ""; @@ -10562,6 +14108,8 @@ CSL.Node.name = { if (state.inheritOpt(this, "delimiter-precedes-last") === "always") { this.and_prefix_single = state.tmp.name_delimiter; } else if (state.inheritOpt(this, "delimiter-precedes-last") === "never") { + // Slightly fragile: could test for charset here to make + // this more certain. if (this.and_prefix_multiple) { this.and_prefix_multiple = " "; } @@ -10573,6 +14121,7 @@ CSL.Node.name = { this.and_prefix_multiple = " "; } } + this.and = {}; if (state.inheritOpt(this, "and")) { state.output.append(this.and_term, "empty", true); @@ -10584,6 +14133,7 @@ CSL.Node.name = { this.and.multiple.strings.prefix = this.and_prefix_multiple; this.and.multiple.strings.suffix = this.and_suffix; } else if (state.tmp.name_delimiter) { + // This is a little weird, but it works. this.and.single = new CSL.Blob(state.tmp.name_delimiter); this.and.single.strings.prefix = ""; this.and.single.strings.suffix = ""; @@ -10591,9 +14141,21 @@ CSL.Node.name = { this.and.multiple.strings.prefix = ""; this.and.multiple.strings.suffix = ""; } + this.ellipsis = {}; if (state.inheritOpt(this, "et-al-use-last")) { + // We use the dedicated Unicode ellipsis character because + // it is recommended by some editors, and can be more easily + // identified for find and replace operations. + // Source: http://en.wikipedia.org/wiki/Ellipsis#Computer_representations + // + + // Eventually, this should be localized as a term in CSL, with some + // mechanism for triggering appropriate punctuation handling around + // the ellipsis placeholder (Polish is a particularly tough case for that). this.ellipsis_term = "\u2026"; + // Similar treatment to "and", above, will be needed + // here when this becomes a locale term. this.ellipsis_prefix_single = " "; this.ellipsis_prefix_multiple = state.inheritOpt(this, "delimiter", "name-delimiter", ", "); this.ellipsis_suffix = " "; @@ -10604,6 +14166,22 @@ CSL.Node.name = { this.ellipsis.multiple.strings.prefix = this.ellipsis_prefix_multiple; this.ellipsis.multiple.strings.suffix = this.ellipsis_suffix; } + + // et-al parameters are annoyingly incomprehensible + // again. + // + // Explanation probably just adds a further layer of + // irritation, but what's INTENDED here is that + // the state.tmp et-al variables are set from the + // cs:key element when composing sort keys, and a + // macro containing a name can be called from cs:key. + // So when cs:key sets et-al attributes, they are + // set on state.tmp, and when the key is finished + // processing, the state.tmp variables are reset to + // undefined. IN THEORY the state.tmp et-al variables + // will not be used in other contexts. I hope. + // + // Anyway, the current tests now seem to pass. if ("undefined" === typeof state.tmp["et-al-min"]) { state.tmp["et-al-min"] = state.inheritOpt(this, "et-al-min"); } @@ -10611,28 +14189,43 @@ CSL.Node.name = { state.tmp["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first"); } if ("undefined" === typeof state.tmp["et-al-use-last"]) { + //print(" setting et-al-use-last from name: "+this.strings["et-al-use-last"]); state.tmp["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last"); } + state.nameOutput.name = this; }; + state.build.name_flag = true; + this.execs.push(func); } target.push(this); } }; + + + +/*global CSL: true */ + CSL.Node["name-part"] = { build: function (state) { state.build[this.strings.name] = this; } }; + +/*global CSL: true */ + CSL.Node.names = { build: function (state, target) { var func; + // CSL.debug = print; + if (this.tokentype === CSL.START || this.tokentype === CSL.SINGLETON) { CSL.Util.substituteStart.call(this, state, target); state.build.substitute_level.push(1); } + if (this.tokentype === CSL.SINGLETON) { state.build.names_variables[state.build.names_variables.length-1].concat(this.variables); for (var i in this.variables) { @@ -10647,20 +14240,28 @@ CSL.Node.names = { }; this.execs.push(func); } + if (this.tokentype === CSL.START) { + state.build.names_flag = true; state.build.name_flag = false; state.build.names_level += 1; state.build.names_variables.push(this.variables); state.build.name_label.push({}); + // init can substitute + // init names func = function (state) { state.tmp.can_substitute.push(true); - state.parallel.StartVariable("names",this.variables[0]); state.nameOutput.init(this); }; this.execs.push(func); + } + if (this.tokentype === CSL.END) { + + // Set/reset name blobs if they exist, for processing + // by namesOutput() for (var i = 0, ilen = 3; i < ilen; i += 1) { var key = ["family", "given", "et-al"][i]; this[key] = state.build[key]; @@ -10668,18 +14269,31 @@ CSL.Node.names = { state.build[key] = undefined; } } + // Labels, if any this.label = state.build.name_label[state.build.name_label.length-1]; state.build.names_level += -1; state.build.names_variables.pop(); state.build.name_label.pop(); + + // The with term. This isn't the right place + // for this, but it's all hard-wired at the + // moment. + + // "and" and "ellipsis" are set in node_name.js func = function (state) { + // Et-al (strings only) + // Blob production has to happen inside nameOutput() + // since proper escaping requires access to the output + // queue. if (state.tmp.etal_node) { this.etal_style = state.tmp.etal_node; } else { this.etal_style = "empty"; } + this.etal_term = state.getTerm(state.tmp.etal_term, "long", 0); this.etal_prefix_single = " "; + // Should be name delimiter, not hard-wired. this.etal_prefix_multiple = state.tmp.name_delimiter; if (state.tmp["delimiter-precedes-et-al"] === "always") { this.etal_prefix_single = state.tmp.name_delimiter; @@ -10691,6 +14305,9 @@ CSL.Node.names = { } this.etal_suffix = ""; if (!CSL.STARTSWITH_ROMANESQUE_REGEXP.test(this.etal_term)) { + // Not sure what the correct treatment is here, but we should not suppress + // a comma-space. + // https://forums.zotero.org/discussion/76679/delimiter-precedes-et-al-always-dose-not-work-in-locale-zh-cn if (this.etal_prefix_single === " ") { this.etal_prefix_single = ""; } @@ -10701,11 +14318,17 @@ CSL.Node.names = { this.etal_suffix = ""; } } + // et-al affixes are further adjusted in nameOutput(), + // after the term (possibly changed in cs:et-al) is known. + + for (var i = 0, ilen = 3; i < ilen; i += 1) { var key = ["family", "given"][i]; state.nameOutput[key] = this[key]; } state.nameOutput["with"] = this["with"]; + + // REMOVE THIS var mywith = "with"; var with_default_prefix = ""; var with_suffix = ""; @@ -10732,7 +14355,12 @@ CSL.Node.names = { thewith.multiple.strings.prefix = with_default_prefix; } state.nameOutput["with"] = thewith; + + + // XXX label style should be set per variable, since they may differ + // XXX with full-form nested names constructs state.nameOutput.label = this.label; + state.nameOutput.etal_style = this.etal_style; state.nameOutput.etal_term = this.etal_term; state.nameOutput.etal_prefix_single = this.etal_prefix_single; @@ -10744,29 +14372,51 @@ CSL.Node.names = { state.tmp["et-al-use-last"] = undefined; }; this.execs.push(func); + + // unsets func = function (state) { if (!state.tmp.can_substitute.pop()) { state.tmp.can_substitute.replace(false, CSL.LITERAL); } - state.parallel.CloseVariable("names"); + + // For posterity ... + // + // This was enough to fix the issue reported here: + // + // http://forums.zotero.org/discussion/25223/citeproc-bug-substitute-doesnt-work-correctly-for-title-macro/ + // + // The remainder of the changes applied in the same patch + // relate to a label assignments, which were found to be + // buggy while working on the issue. The test covering + // both problems is here: + // + // https://bitbucket.org/bdarcus/citeproc-test/src/ab136a6aa8f2/processor-tests/humans/substitute_SuppressOrdinaryVariable.txt if (state.tmp.can_substitute.mystack.length === 1) { state.tmp.can_block_substitute = false; } }; this.execs.push(func); + state.build.name_flag = false; } target.push(this); + if (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON) { state.build.substitute_level.pop(); CSL.Util.substituteEnd.call(this, state, target); } } }; + +/*global CSL: true */ + CSL.Node.number = { build: function (state, target) { var func; CSL.Util.substituteStart.call(this, state, target); + // + // This should push a rangeable object to the queue. + // if (this.strings.form === "roman") { this.formatter = state.fun.romanizer; } else if (this.strings.form === "ordinal") { @@ -10780,7 +14430,20 @@ CSL.Node.number = { if ("undefined" === typeof this.splice_prefix) { this.splice_prefix = state[state.build.area].opt.layout_delimiter; } + // is this needed? + //if ("undefined" === typeof this.splice_prefix){ + // this.splice_prefix = state[state.tmp.area].opt.layout_delimiter; + //} + // + // Whether we actually stick a number object on + // the output queue depends on whether the field + // contains a pure number. + // + // push number or text func = function (state, Item, item) { + // NOTE: this works because this is the ONLY function in this node. + // If further functions are added, they need to start with the same + // abort condition. if (this.variables.length === 0) { return; } @@ -10801,20 +14464,43 @@ CSL.Node.number = { return; } } - state.parallel.StartVariable(this.variables[0]); - if (this.variables[0] === "locator") { - state.parallel.AppendToVariable(Item.section); - } else { - state.parallel.AppendToVariable(Item[this.variables[0]]); - } + if (varname === 'collection-number' && Item.type === 'legal_case') { state.tmp.renders_collection_number = true; } + + // For bill or legislation items that have a label-form + // attribute set on the cs:number node rendering the locator, + // the form and pluralism of locator terms are controlled + // separately from those of the initial label. Form is + // straightforward: the label uses the value set on + // the cs:label node that renders it, and the embedded + // labels use the value of label-form set on the cs:number + // node. Both default to "long". + // + // Pluralism is more complicated. For embedded labels, + // pluralism is evaluated using a simple heuristic that + // can be found below (it just looks for comma, ampersand etc). + // The item.label rendered independently via cs:label + // defaults to singular. It is always singular if embedded + // labels exist that (when expanded to their valid CSL + // value) do not match the value of item.label. Otherwise, + // if one or more matching embedded labels exist, the + // cs:label is set to plural. + // + // The code that does all this is divided between this module, + // util_static_locator.js, and util_label.js. It's not easy + // to follow, but seems to do the job. Let's home for good + // luck out there in the wild. + var node = this; + if (state.tmp.group_context.tip.force_suppress) { return false; } + if (["locator", "locator-extra"].indexOf(varname) > -1) { + // amazing that we reach this. should abort sooner if no content? state.processNumber.call(state, node, item, varname, Item.type); } else { if (!state.tmp.group_context.tip.condition) { @@ -10822,8 +14508,9 @@ CSL.Node.number = { } state.processNumber.call(state, node, Item, varname, Item.type); } + CSL.Util.outputNumericField(state, varname, Item.id); - state.parallel.CloseVariable("number"); + if (["locator", "locator-extra"].indexOf(this.variables_real[0]) > -1 && !state.tmp.just_looking) { state.tmp.done_vars.push(this.variables_real[0]); @@ -10832,9 +14519,20 @@ CSL.Node.number = { }; this.execs.push(func); target.push(this); + CSL.Util.substituteEnd.call(this, state, target); } }; + +/*global CSL: true */ + +/* + * Yikes, these functions were running out of scope for yonks. + * now that they are set in the correct token list, + * they might be useful for things. + * FB 2013.11.09 +*/ + CSL.Node.sort = { build: function (state, target) { target = state[state.build.root + "_sort"].tokens; @@ -10845,7 +14543,10 @@ CSL.Node.sort = { } state.build.area = state.build.root + "_sort"; state.build.extension = "_sort"; + var func = function (state, Item) { + //state.tmp.area = state.tmp.root + "_sort"; + //state.tmp.extension = "_sort"; if (state.opt.has_layout_locale) { var langspec = CSL.localeResolve(Item.language, state.opt["default-locale"][0]); var sort_locales = state[state.tmp.area.slice(0,-5)].opt.sort_locales; @@ -10867,6 +14568,7 @@ CSL.Node.sort = { } }; this.execs.push(func); + } if (this.tokentype === CSL.END) { state.build.area = state.build.root; @@ -10876,16 +14578,32 @@ CSL.Node.sort = { state.opt.lang = state.tmp.lang_sort_hold; delete state.tmp.lang_sort_hold; } + //state.tmp.area = state.tmp.root; + //state.tmp.extension = ""; }; this.execs.push(func); + /* + var func = function (state, Item) { + state.tmp.area = state.tmp.root; + state.tmp.extension = ""; + } + this.execs.push(func); + */ } target.push(this); } }; + + + +/*global CSL: true */ + CSL.Node.substitute = { build: function (state, target) { var func; if (this.tokentype === CSL.START) { + /* */ + // set conditional var choose_start = new CSL.Token("choose", CSL.START); CSL.Node.choose.build.call(choose_start, state, target); var if_singleton = new CSL.Token("if", CSL.SINGLETON); @@ -10898,6 +14616,7 @@ CSL.Node.substitute = { if_singleton.tests = [func]; if_singleton.test = state.fun.match.any(if_singleton, state, if_singleton.tests); target.push(if_singleton); + func = function (state) { state.tmp.can_block_substitute = true; if (state.tmp.value.length && !state.tmp.common_term_match_fail) { @@ -10907,14 +14626,24 @@ CSL.Node.substitute = { }; this.execs.push(func); target.push(this); + /* */ } if (this.tokentype === CSL.END) { + //var if_end = new CSL.Token("if", CSL.END); + //CSL.Node["if"].build.call(if_end, state, target); + /* */ target.push(this); var choose_end = new CSL.Token("choose", CSL.END); CSL.Node.choose.build.call(choose_end, state, target); + /* */ } } }; + + + +/*global CSL: true */ + CSL.Node.text = { build: function (state, target) { var func, form, plural, id, num, number, formatter, firstoutput, specialdelimiter, label, suffix, term; @@ -10923,7 +14652,9 @@ CSL.Node.text = { group_start.name = "group"; group_start.tokentype = CSL.START; CSL.Node.group.build.call(group_start, state, target); + CSL.expandMacro.call(state, this, target); + var group_end = CSL.Util.cloneToken(this); group_end.name = "group"; group_end.tokentype = CSL.END; @@ -10931,14 +14662,24 @@ CSL.Node.text = { group_end.isJurisLocatorLabel = true; } CSL.Node.group.build.call(group_end, state, target); + } else { CSL.Util.substituteStart.call(this, state, target); + // ... + // + // Do non-macro stuff + + // Guess again. this.variables is ephemeral, adjusted by an initial + // function set on the node via @variable attribute setup. + //variable = this.variables[0]; + if (!this.variables_real) { this.variables_real = []; } if (!this.variables) { this.variables = []; } + form = "long"; plural = 0; if (this.strings.form) { @@ -10948,19 +14689,27 @@ CSL.Node.text = { plural = this.strings.plural; } if ("citation-number" === this.variables_real[0] || "year-suffix" === this.variables_real[0] || "citation-label" === this.variables_real[0]) { + // + // citation-number and year-suffix are super special, + // because they are rangeables, and require a completely + // different set of formatting parameters on the output + // queue. if (this.variables_real[0] === "citation-number") { + if (state.build.root === "citation") { state.opt.update_mode = CSL.NUMERIC; } if (state.build.root === "bibliography") { state.opt.bib_mode = CSL.NUMERIC; } + //this.strings.is_rangeable = true; if ("citation-number" === state[state.tmp.area].opt.collapse) { this.range_prefix = state.getTerm("citation-range-delimiter"); } this.successor_prefix = state[state.build.area].opt.layout_delimiter; this.splice_prefix = state[state.build.area].opt.layout_delimiter; func = function (state, Item, item) { + id = "" + Item.id; if (!state.tmp.just_looking) { if (state.tmp.area.slice(-5) === "_sort" && this.variables[0] === "citation-number") { @@ -10973,6 +14722,7 @@ CSL.Node.text = { var num = state.registry.registry[Item.id].seq; } if (num) { + // Code currently in util_number.js num = CSL.Util.padding("" + num); } state.output.append(num, this); @@ -10999,8 +14749,11 @@ CSL.Node.text = { }; this.execs.push(func); } else if (this.variables_real[0] === "year-suffix") { + state.opt.has_year_suffix = true; + if (state[state.tmp.area].opt.collapse === "year-suffix-ranged") { + //this.range_prefix = "-"; this.range_prefix = state.getTerm("citation-range-delimiter"); } this.successor_prefix = state[state.build.area].opt.layout_delimiter; @@ -11009,7 +14762,14 @@ CSL.Node.text = { } func = function (state, Item) { if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false && !state.tmp.just_looking) { + //state.output.append(state.registry.registry[Item.id].disambig[2],this); num = parseInt(state.registry.registry[Item.id].disambig.year_suffix, 10); + + //if (state[state.tmp.area].opt.collapse === "year-suffix-ranged") { + // //this.range_prefix = "-"; + // this.range_prefix = state.getTerm("citation-range-delimiter"); + //} + //this.successor_prefix = state[state.tmp.area].opt.layout_delimiter; if (state[state.tmp.area].opt.cite_group_delimiter) { this.successor_prefix = state[state.tmp.area].opt.cite_group_delimiter; } @@ -11018,6 +14778,7 @@ CSL.Node.text = { number.setFormatter(formatter); state.output.append(number, "literal"); firstoutput = false; + // XXX Can we do something better for length here? for (var i=0,ilen=state.tmp.group_context.mystack.length; i -1 || ["language-name", "language-name-original"].indexOf(this.variables_real[0]) > -1) { + + // multi-fields + // Initialize transform factory according to whether + // abbreviation is desired. var abbrevfam = this.variables[0]; var abbrfall = false; var altvar = false; @@ -11117,18 +14903,34 @@ CSL.Node.text = { abbrevfam = false; } if (state.build.extension) { + // multi-fields for sorting get a sort transform, + // (abbreviated if the short form was selected) transfall = true; } else { transfall = true; abbrfall = true; } + func = state.transform.getOutputFunction(this.variables, abbrevfam, abbrfall, altvar, transfall); } else { + // ordinary fields if (CSL.CITE_FIELDS.indexOf(this.variables_real[0]) > -1) { + // per-cite fields are read from item, rather than Item func = function (state, Item, item) { if (item && item[this.variables[0]]) { + // Code copied to page variable as well; both + // become cs:number in MLZ extended schema + + // If locator, use cs:number. Otherwise, render + // normally. + + // XXX The code below is pretty-much copied from + // XXX node_number.js. Should be a common function. + // XXX BEGIN state.processNumber(this, item, this.variables[0], Item.type); CSL.Util.outputNumericField(state, this.variables[0], Item.id); + // XXX END + if (["locator", "locator-extra"].indexOf(this.variables_real[0]) > -1 && !state.tmp.just_looking) { state.tmp.done_vars.push(this.variables_real[0]); @@ -11136,6 +14938,8 @@ CSL.Node.text = { } }; } else if (["page", "page-first", "chapter-number", "collection-number", "edition", "issue", "number", "number-of-pages", "number-of-volumes", "volume"].indexOf(this.variables_real[0]) > -1) { + // page gets mangled with the correct collapsing + // algorithm func = function(state, Item) { state.processNumber(this, Item, this.variables[0], Item.type); CSL.Util.outputNumericField(state, this.variables[0], Item.id); @@ -11146,36 +14950,55 @@ CSL.Node.text = { if (this.variables[0]) { value = state.getVariable(Item, this.variables[0], form); if (value) { + // true is for non-suppression of periods if (state.opt.development_extensions.wrap_url_and_doi) { if (!this.decorations.length || this.decorations[0][0] !== "@" + this.variables[0]) { + // Special-casing to fix https://github.com/Juris-M/citeproc-js/issues/57 + // clone current token, to avoid collateral damage var clonetoken = CSL.Util.cloneToken(this); + // cast a group blob var groupblob = new CSL.Blob(null, null, "url-wrapper"); + // set the DOI decoration on the blob groupblob.decorations.push(["@DOI", "true"]); if (this.variables_real[0] === "DOI") { + // strip a proper DOI prefix var prefix; if (this.strings.prefix && this.strings.prefix.match(/^.*https:\/\/doi\.org\/$/)) { value = value.replace(/^https?:\/\/doi\.org\//, ""); if (value.match(/^https?:\/\//)) { + // Do not tamper with another protocol + domain if already set in field value prefix = ""; } else { + // Otherwise https + domain prefix = "https://doi.org/"; } + // set any string prefix on the clone clonetoken.strings.prefix = this.strings.prefix.slice(0, clonetoken.strings.prefix.length-16); } + // cast a text blob + // set the prefix as the content of the blob var prefixblob = new CSL.Blob(prefix); + // cast another text blob + // set the value as the content of the second blob var valueblob = new CSL.Blob(value); + // append new text token and clone to group token groupblob.push(prefixblob); groupblob.push(valueblob); + // append group token to output state.output.append(groupblob, clonetoken, false, false, true); } else { var valueblob = new CSL.Blob(value); + // append new text token and clone to group token groupblob.push(valueblob); + // append group token to output + //this.decorations = [["@" + this.variables[0], "true"]].concat(this.decorations); state.output.append(groupblob, clonetoken, false, false, true); } } else { state.output.append(value, this, false, false, true); } } else { + // This is totally unnecessary, isn't it? if (this.decorations.length) { for (var i=this.decorations.length-1; i>-1; i--) { if (this.decorations[i][0] === "@" + this.variables[0]) { @@ -11189,6 +15012,9 @@ CSL.Node.text = { } }; } else if (this.variables_real[0] === "section") { + // Sections for statutes are special. This is an uncommon + // variable, so we save the cost of the runtime check + // unless it's being used. func = function (state, Item) { var value; value = state.getVariable(Item, this.variables[0], form); @@ -11205,6 +15031,7 @@ CSL.Node.text = { } }; } else { + // anything left over just gets output in the normal way. func = function (state, Item) { var value; if (this.variables[0]) { @@ -11219,17 +15046,16 @@ CSL.Node.text = { } } this.execs.push(func); - func = function (state) { - state.parallel.CloseVariable("text"); - }; - this.execs.push(func); } else if (this.strings.value) { + // for the text value attribute. func = function (state) { state.tmp.group_context.tip.term_intended = true; + // true flags that this is a literal-value term CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, this.strings.value, true); state.output.append(this.strings.value, this); }; this.execs.push(func); + // otherwise no output } } target.push(this); @@ -11237,12 +15063,19 @@ CSL.Node.text = { } } }; + + + +/*global CSL: true */ + CSL.Node.intext = { build: function (state, target) { if (this.tokentype === CSL.START) { + state.build.area = "intext"; state.build.root = "intext"; state.build.extension = ""; + var func = function(state, Item) { state.tmp.area = "intext"; state.tmp.root = "intext"; @@ -11251,23 +15084,24 @@ CSL.Node.intext = { this.execs.push(func); } if (this.tokentype === CSL.END) { - state.opt.grouped_sort = state.opt.xclass === "in-text" - && (state.citation.opt.collapse - && state.citation.opt.collapse.length) - || (state.citation.opt.cite_group_delimiter - && state.citation.opt.cite_group_delimiter.length) - && state.opt.update_mode !== CSL.POSITION - && state.opt.update_mode !== CSL.NUMERIC; - if (state.opt.grouped_sort - && state.citation_sort.opt.sort_directions.length) { - state.intext_sort.opt.sort_directions = state.citation_sort.opt.sort_directions; + + // Do whatever cs:citation does with sorting. + state.intext_sort = { + opt: { + sort_directions: state.citation_sort.opt.sort_directions + } } state.intext.srt = state.citation.srt; } target.push(this); } }; + + +/*global CSL: true */ + CSL.Attributes = {}; + CSL.Attributes["@genre"] = function (state, arg) { this.tests ? {} : this.tests = []; arg = arg.replace("-", " "); @@ -11279,6 +15113,7 @@ CSL.Attributes["@genre"] = function (state, arg) { }; this.tests.push(func); }; + CSL.Attributes["@disambiguate"] = function (state, arg) { this.tests ? {} : this.tests = []; if (arg === "true") { @@ -11310,6 +15145,7 @@ CSL.Attributes["@disambiguate"] = function (state, arg) { this.tests.push(func); } }; + CSL.Attributes["@is-numeric"] = function (state, arg) { this.tests ? {} : this.tests = []; var variables = arg.split(/\s+/); @@ -11341,9 +15177,12 @@ CSL.Attributes["@is-numeric"] = function (state, arg) { this.tests.push(maketest(variables[i])); } }; + + CSL.Attributes["@is-uncertain-date"] = function (state, arg) { this.tests ? {} : this.tests = []; var variables = arg.split(/\s+/); + // Strip off any boolean prefix. var maketest = function (myvariable) { return function(Item) { if (Item[myvariable] && Item[myvariable].circa) { @@ -11357,10 +15196,13 @@ CSL.Attributes["@is-uncertain-date"] = function (state, arg) { this.tests.push(maketest(variables[i])); } }; + + CSL.Attributes["@locator"] = function (state, arg) { this.tests ? {} : this.tests = []; var trylabels = arg.replace("sub verbo", "sub-verbo"); trylabels = trylabels.split(/\s+/); + // Strip off any boolean prefix. var maketest = function (trylabel) { return function(Item, item) { var label; @@ -11377,6 +15219,8 @@ CSL.Attributes["@locator"] = function (state, arg) { this.tests.push(maketest(trylabels[i])); } }; + + CSL.Attributes["@position"] = function (state, arg) { this.tests ? {} : this.tests = []; var tryposition; @@ -11435,9 +15279,12 @@ CSL.Attributes["@position"] = function (state, arg) { } } }; + CSL.Attributes["@type"] = function (state, arg) { this.tests ? {} : this.tests = []; + // XXX This is ALWAYS composed as an "any" match var types = arg.split(/\s+/); + // Strip off any boolean prefix. var maketest = function (mytype) { return function(Item) { var ret = (Item.type === mytype); @@ -11454,20 +15301,35 @@ CSL.Attributes["@type"] = function (state, arg) { } this.tests.push(state.fun.match.any(this, state, tests)); }; + CSL.Attributes["@variable"] = function (state, arg) { this.tests ? {} : this.tests = []; var func; this.variables = arg.split(/\s+/); this.variables_real = this.variables.slice(); + + // First the non-conditional code. if ("label" === this.name && this.variables[0]) { this.strings.term = this.variables[0]; } else if (["names", "date", "text", "number"].indexOf(this.name) > -1) { + // + // An oddity of variable handling is that this.variables + // is actually ephemeral; the full list of variables is + // held in the variables_real var, and pushed into this.variables + // conditionally in order to suppress repeat renderings of + // the same item variable. [STILL FUNCTIONAL? 2010.01.15] + // + // set variable names func = function (state, Item, item) { + // Clear this.variables in place for (var i = this.variables.length - 1; i > -1; i += -1) { this.variables.pop(); } for (var i=0,ilen=this.variables_real.length;i -1 && "string" === typeof Item[variable] && "names" === this.name) { + + // Great! So for each of these, we split. + // And we only recombine everything if the length + // of all the splits matches. + + // Preflight var isValid = true; var rawNames = Item[variable].split(/\s*;\s*/); var rawMultiNames = {}; @@ -11525,10 +15395,15 @@ CSL.Attributes["@variable"] = function (state, arg) { } } if (variable === "year-suffix") { + // year-suffix always signals that it produces output, + // even when it doesn't. This permits it to be used with + // the "no date" term inside a group used exclusively + // to control formatting. output = true; break; } else if (CSL.DATE_VARIABLES.indexOf(variable) > -1) { if (state.opt.development_extensions.locator_date_and_revision && "locator-date" === variable) { + // If locator-date is set, it's valid. output = true; break; } @@ -11568,6 +15443,7 @@ CSL.Attributes["@variable"] = function (state, arg) { if (state.transform.abbrevs["default"].hereinafter[Item.id] && state.sys.getAbbreviation && Item.id) { + output = true; } break; @@ -11584,33 +15460,45 @@ CSL.Attributes["@variable"] = function (state, arg) { break; } } + //print("-- VAR: "+variable); + //flag = state.tmp.group_context.tip; if (output) { for (var i=0,ilen=this.variables_real.length;i -1) { + // Strip off any boolean prefix. + // Now the conditionals. var maketest = function (variable) { return function(Item,item){ var myitem = Item; if (item && ["locator", "locator-extra", "first-reference-note-number", "locator-date"].indexOf(variable) > -1) { myitem = item; } + // We don't run loadAbbreviation() here; it is run by the application-supplied + // retrieveItem() if hereinafter functionality is to be used, so this key will + // always exist in memory, possibly with a nil value. if (variable === "hereinafter" && state.sys.getAbbreviation && myitem.id) { if (state.transform.abbrevs["default"].hereinafter[myitem.id]) { return true; @@ -11619,6 +15507,11 @@ CSL.Attributes["@variable"] = function (state, arg) { if ("number" === typeof myitem[variable] || "string" === typeof myitem[variable]) { return true; } else if ("object" === typeof myitem[variable]) { + // + // this will turn true only for hash objects + // that have at least one attribute, or for a + // non-zero-length list + // for (var key in myitem[variable]) { if (myitem[variable][key]) { return true; @@ -11634,10 +15527,13 @@ CSL.Attributes["@variable"] = function (state, arg) { } } }; + + CSL.Attributes["@page"] = function (state, arg) { this.tests ? {} : this.tests = []; var trylabels = arg.replace("sub verbo", "sub-verbo"); trylabels = trylabels.split(/\s+/); + // Strip off any boolean prefix. var maketest = function (trylabel) { return function(Item) { var label; @@ -11660,6 +15556,9 @@ CSL.Attributes["@page"] = function (state, arg) { this.tests.push(maketest(trylabels[i])); } }; + + +// a near duplicate of code above CSL.Attributes["@number"] = function (state, arg) { this.tests ? {} : this.tests = []; var trylabels = arg.split(/\s+/); @@ -11683,9 +15582,11 @@ CSL.Attributes["@number"] = function (state, arg) { this.tests.push(maketest(trylabels[i])); } }; + CSL.Attributes["@jurisdiction"] = function (state, arg) { this.tests ? {} : this.tests = []; var tryjurisdictions = arg.split(/\s+/); + // Strip off any boolean prefix. for (var i=0,ilen=tryjurisdictions.length;i -1) { state.opt.lang = me.locale; state.tmp.last_cite_locale = me.locale; + // Set empty group open tag with locale set marker state.output.openLevel("empty"); state.output.current.value().new_locale = me.locale; res = true; @@ -11991,74 +15956,157 @@ CSL.Attributes["@locale-internal"] = function (state, arg) { var me = this; this.tests.push(maketest(me)); }; + + +// These are not evaluated as conditions immediately: they only +// set parameters that are picked up during processing. CSL.Attributes["@is-parallel"] = function (state, arg) { - var values = arg.split(" "); - for (var i = 0, ilen = values.length; i < ilen; i += 1) { - if (values[i] === "true") { - values[i] = true; - } else if (values[i] === "false") { - values[i] = false; - } - } - this.strings.set_parallel_condition = values; + this.strings.set_parallel_condition = arg; }; +CSL.Attributes["@no-repeat"] = function (state, arg) { + this.strings.set_no_repeat_condition = arg.split(/\s+/); +}; + + CSL.Attributes["@jurisdiction-depth"] = function (state, arg) { this.strings.jurisdiction_depth = parseInt(arg, 10); }; + + CSL.Attributes["@require"] = function (state, arg) { this.strings.require = arg; + + // Introduced to constrain rendering of the group with a + // requirement that it either render an alpha term via cs:label or + // cs:text at least once, or render without any label. That + // behaviour is invoked with "label-empty-or-alpha" as arg. + + // This attribute is a complement to @label-form and modular + // jurisdiction support, as it makes macros that adapt to shifting + // local term definitions possible. }; + CSL.Attributes["@reject"] = function (state, arg) { this.strings.reject = arg; + + // Introduced to constrain rendering of the group with a + // requirement that it render some label via cs:label or cs:text, + // and that it NOT be alpha. That behaviour is invoked with + // "label-empty-or-alpha" as arg. + + // This attribute is a complement to @label-form and modular + // jurisdiction support, as it makes macros that adapt to shifting + // local term definitions possible. }; + CSL.Attributes["@gender"] = function (state, arg) { this.gender = arg; }; + CSL.Attributes["@cslid"] = function (state, arg) { + // @cslid is a noop + // The value set on this attribute is used to + // generate reverse lookup wrappers on output when + // this.development_extensions.csl_reverse_lookup_support is + // set to true in state.js (there is no runtime option, + // it must be set in state.js) + // + // See the @showid method in the html output + // section of formats.js for the function that + // renders the wrappers. this.cslid = parseInt(arg, 10); }; + CSL.Attributes["@capitalize-if-first"] = function (state, arg) { this.strings.capitalize_if_first_override = arg; }; + CSL.Attributes["@label-capitalize-if-first"] = function (state, arg) { this.strings.label_capitalize_if_first_override = arg; }; + CSL.Attributes["@label-form"] = function (state, arg) { this.strings.label_form_override = arg; }; + CSL.Attributes["@part-separator"] = function (state, arg) { this.strings["part-separator"] = arg; }; + CSL.Attributes["@leading-noise-words"] = function (state, arg) { this["leading-noise-words"] = arg; }; + CSL.Attributes["@name-never-short"] = function (state, arg) { this["name-never-short"] = arg; }; + CSL.Attributes["@class"] = function (state, arg) { state.opt["class"] = arg; }; + CSL.Attributes["@version"] = function (state, arg) { state.opt.version = arg; }; + +/** + * Store the value attribute on the token. + * @name CSL.Attributes.@value + * @function + */ CSL.Attributes["@value"] = function (state, arg) { this.strings.value = arg; }; + + +/** + * Store the name attribute (of a macro or term node) + * on the state object. + *

For 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; i 0 && this.sortedItemsPos < this.sortedItems.length) { - curr = this.sortedItems[this.sortedItemsPos][1]; - last_id = "" + this.sortedItems[(this.sortedItemsPos - 1)][1].id; - master = this.state.registry.registry[last_id].parallel; - prev_locator = false; - if (master == curr.id) { - len = this.sortedItemsPos - 1; - for (pos = len; pos > -1; pos += -1) { - if (this.sortedItems[pos][1].id == Item.id) { - prev_locator = this.sortedItems[pos][1].locator; - break; + // On a second pass, set an item to FIRST if the current + // item is in its seeAlso. The seeAlso of the FIRST item control + // until (a) a non-member is encountered at CURRENT, or + // (b) the end of the array is reached. + // The seeAlso keys are deleted as each is seen. + // If neither (a) nor (b), set the current item to MID. + // If (a) and the previous item is FIRST, delete its + // parallel marker. + // If (b) and the current item is FIRST, delete its + // parallel marker. + // If (a) and the previous item is not FIRST, set it to + // LAST, and reset seeAlso from the current item. + // If (b) and the current item is not FIRST, set it to + // LAST. + var seriesRels = false; + var masterID = false; + for (var i=1,ilen=sortedItems.length; i -1) { - return; - } - if (variable === "container-title" && this.sets.value().length === 0) { - this.master_was_neutral_cite = false; - } - this.data = {}; - this.data.value = ""; - this.data.blobs = []; - var is_mid = this.isMid(variable); - if (real_variable === "authority" && this.variable === "names:front" && this.sets.value().length) { - var prev = this.sets.value()[(this.sets.value().length - 1)].Item; - var thisAuthority = false; - if (this.cite.Item.authority && this.cite.Item.authority.length) { - thisAuthority = this.cite.Item.authority[0].literal; - } - var thatAuthority = false; - if (prev.authority && prev.authority.length) { - thatAuthority = prev.authority[0].literal; - } - if (thisAuthority !== thatAuthority) { - this.try_cite = true; - this.in_series = false; - } - } else if (this.target === "front" && is_mid) { - this.target = "mid"; - } else if (this.target === "mid" && !is_mid && this.cite.Item.title && variable !== "names") { - this.target = "back"; - } else if (this.target === "back" && is_mid) { - this.try_cite = true; - this.in_series = false; - } - if (variable === "number") { - this.cite.front.push(this.variable); - } else if (CSL.PARALLEL_COLLAPSING_MID_VARSET.indexOf(variable) > -1) { - if (["article-journal","article-magazine"].indexOf(this.cite.Item.type) > -1) { - this.cite.mid.push(this.variable); } else { - this.cite.front.push(this.variable); - } - } else { - this.cite[this.target].push(this.variable); - } - } -}; -CSL.Parallel.prototype.AppendBlobPointer = function (blob) { - if (this.use_parallels) { - if (this.ignoreVars.indexOf(this.variable) > -1) { - return; - } - if (this.use_parallels && (this.force_collapse || this.try_cite)) { - if (["article-journal", "article-magazine"].indexOf(this.cite.Item.type) > -1) { - if (["volume","page","page-first","issue"].indexOf(this.variable) > -1) { - return; - } - if ("container-title" === this.variable && this.cite.mid.length > 1) { - return; - } - } - if (this.variable && (this.try_cite || this.force_collapse) && blob && blob.blobs) { - if (!(this.cite.useProceduralHistory && this.target === "back")) { - this.data.blobs.push([blob, blob.blobs.length]); - } - } - } - } -}; -CSL.Parallel.prototype.AppendToVariable = function (str) { - if (this.use_parallels) { - if (this.ignoreVars.indexOf(this.variable) > -1) { - return; - } - if (this.try_cite || this.force_collapse) { - if (this.target !== "back" || true) { - this.data.value += "::" + str; - } else { - var prev = this.sets.value()[(this.sets.value().length - 1)]; - if (prev) { - if (prev[this.variable]) { - if (prev[this.variable].value) { - this.data.value += "::" + str; - } + if (seriesRels[curr.id]) { + sortedItems[i][1].parallel = "mid"; + if (!sortedItems[i][1].prefix) { + sortedItems[i][1].prefix = ", "; } - } - } - } - } -}; -CSL.Parallel.prototype.CloseVariable = function () { - if (this.use_parallels) { - if (this.ignoreVars.indexOf(this.variable) > -1) { - return; - } - if (this.try_cite || this.force_collapse) { - this.cite[this.variable] = this.data; - if (this.sets.value().length > 0) { - var prev = this.sets.value()[(this.sets.value().length - 1)]; - if (this.target === "front" && this.variable === "issued") { - if (this.data.value && this.master_was_neutral_cite) { - this.target = "mid"; - } - } - if (this.target === "front") { - if ((prev[this.variable] || this.data.value) && (!prev[this.variable] || this.data.value !== prev[this.variable].value)) { - if ("issued" !== this.variable) { - this.in_series = false; - } - } - } else if (this.target === "mid") { - if (CSL.PARALLEL_COLLAPSING_MID_VARSET.indexOf(this.variable) > -1) { - if (prev[this.variable]) { - if (prev[this.variable].value === this.data.value) { - this.cite.front_collapse[this.variable] = true; - } else { - this.cite.front_collapse[this.variable] = false; - } - } else { - this.cite.front_collapse[this.variable] = false; - } - } - } else if (this.target === "back") { - if (prev[this.variable]) { - if (this.data.value !== prev[this.variable].value - && this.sets.value().slice(-1)[0].back_forceme.indexOf(this.variable) === -1) { - this.in_series = false; - } - } - } - } - } - this.variable = false; - } -}; -CSL.Parallel.prototype.CloseCite = function () { - var x, pos, len, use_journal_info, volume_pos, container_title_pos, section_pos; - if (this.use_parallels && (this.force_collapse || this.try_cite)) { - use_journal_info = false; - if (!this.cite.front_collapse["container-title"]) { - use_journal_info = true; - } - if (this.cite.front_collapse.volume === false) { - use_journal_info = true; - } - if (this.cite.front_collapse["collection-number"] === false) { - use_journal_info = true; - } - if (this.cite.front_collapse.section === false) { - use_journal_info = true; - } - if (use_journal_info) { - this.cite.use_journal_info = true; - section_pos = this.cite.front.indexOf("section"); - if (section_pos > -1) { - this.cite.front = this.cite.front.slice(0,section_pos).concat(this.cite.front.slice(section_pos + 1)); - } - volume_pos = this.cite.front.indexOf("volume"); - if (volume_pos > -1) { - this.cite.front = this.cite.front.slice(0,volume_pos).concat(this.cite.front.slice(volume_pos + 1)); - } - container_title_pos = this.cite.front.indexOf("container-title"); - if (container_title_pos > -1) { - this.cite.front = this.cite.front.slice(0,container_title_pos).concat(this.cite.front.slice(container_title_pos + 1)); - } - var collection_number_pos = this.cite.front.indexOf("collection-number"); - if (collection_number_pos > -1) { - this.cite.front = this.cite.front.slice(0,collection_number_pos).concat(this.cite.front.slice(collection_number_pos + 1)); - } - } - if (!this.in_series && !this.force_collapse) { - this.ComposeSet(true); - } - if (this.sets.value().length === 0) { - var has_date = false; - for (pos = 0, len = this.cite.back.length; pos < len; pos += 1) { - x = this.cite.back[pos]; - if (x === "issued" && this.cite["issued"] && this.cite["issued"].value) { - has_date = true; - break; - } - } - if (!has_date) { - this.cite.back_forceme.push("issued"); - } - } else { - var idx = this.cite.front.indexOf("issued"); - if (idx === -1 || this.master_was_neutral_cite) { - this.cite.back_forceme = this.sets.value().slice(-1)[0].back_forceme; - } - if (idx > -1) { - var prev = this.sets.value()[this.sets.value().length - 1]; - if (!prev["issued"]) { - this.cite.front = this.cite.front.slice(0, idx).concat(this.cite.front.slice(idx + 1)); - } - } - if (this.master_was_neutral_cite && this.cite.mid.indexOf("names:mid") > -1) { - this.cite.front.push("names:mid"); - } - } - this.sets.value().push(this.cite); - } -}; -CSL.Parallel.prototype.ComposeSet = function () { - var cite, pos, len; - if (this.use_parallels && (this.force_collapse || this.try_cite)) { - var lengthCheck = this.sets.value().length; - if (this.sets.value().length === 1) { - if (!this.in_series) { - this.sets.value().pop(); - this.delim_counter += 1; - } - } else { - len = this.sets.value().length; - for (pos = 0; pos < len; pos += 1) { - cite = this.sets.value()[pos]; - if (pos === 0) { - this.delim_counter += 1; + delete seriesRels[curr.id]; + sortedItems[i][1].repeats = this.getRepeats(prev, curr); + this.state.registry.registry[masterID].siblings.push(curr.id); } else { - if (!cite.Item.title && cite.use_journal_info) { - this.delim_pointers.push(false); - } else { - this.delim_pointers.push(this.delim_counter); - } - this.delim_counter += 1; - } - if (CSL.POSITION_FIRST === cite.position) { - if (pos === 0) { - this.state.registry.registry[cite.itemId].master = true; - this.state.registry.registry[cite.itemId].siblings = []; - this.state.registry.registry[cite.itemId].parallel = false; - } else { - if (cite.prevItemID) { - if (!this.state.registry.registry[cite.prevItemID].parallel) { - this.state.registry.registry[cite.itemId].parallel = cite.prevItemID; - } else { - this.state.registry.registry[cite.itemId].parallel = this.state.registry.registry[cite.prevItemID].parallel; - } - this.state.registry.registry[cite.itemId].siblings = this.state.registry.registry[cite.prevItemID].siblings; - if (!this.state.registry.registry[cite.itemId].siblings) { - this.state.registry.registry[cite.itemId].siblings = []; - CSL.debug("WARNING: adding missing siblings array to registry object"); - } - this.state.registry.registry[cite.itemId].siblings.push(cite.itemId); - } - } + sortedItems[i-1][1].parallel = "last"; + seriesRels = false; } } - this.sets.push([]); - } - if (lengthCheck < 2) { - this.purgeGroupsIfParallel(false); - } else { - this.purgeGroupsIfParallel(true); - } - this.in_series = true; - } -}; -CSL.Parallel.prototype.PruneOutputQueue = function () { - var len, pos, series, ppos, llen, cite; - if (this.use_parallels) { - len = this.sets.mystack.length; - for (pos = 0; pos < len; pos += 1) { - series = this.sets.mystack[pos]; - if (series.length > 1) { - llen = series.length; - for (ppos = 0; ppos < llen; ppos += 1) { - cite = series[ppos]; - if (ppos === 0) { - this.purgeVariableBlobs(cite, cite.back); - } else if (ppos === (series.length - 1)) { - this.purgeVariableBlobs(cite, cite.front.concat(cite.back_forceme)); - } else { - this.purgeVariableBlobs(cite, cite.front.concat(cite.back)); - } + if (i === (sortedItems.length-1)) { + if (sortedItems[i][1].parallel === "mid") { + sortedItems[i][1].parallel = "last"; } } } } }; -CSL.Parallel.prototype.purgeVariableBlobs = function (cite, varnames) { - var len, pos, varname, b, llen, ppos, out; - if (this.use_parallels) { - out = this.state.output.current.value(); - if ("undefined" === typeof out.length) { - out = out.blobs; - } - for (pos = 0, len = this.delim_pointers.length; pos < len; pos += 1) { - ppos = this.delim_pointers[pos]; - if (ppos !== false) { - out[ppos].parallel_delimiter = ", "; - } - } - len = varnames.length - 1; - for (pos = len; pos > -1; pos += -1) { - varname = varnames[pos]; - if (cite[varname]) { - llen = cite[varname].blobs.length - 1; - for (ppos = llen; ppos > -1; ppos += -1) { - b = cite[varname].blobs[ppos]; - b[0].blobs = b[0].blobs.slice(0, b[1]).concat(b[0].blobs.slice((b[1] + 1))); - this.state.tmp.has_purged_parallel = true; - if (b[0] && b[0].strings && "string" == typeof b[0].strings.oops - && b[0].parent && b[0].parent) { - b[0].parent.parent.strings.delimiter = b[0].strings.oops; - } - } - } - } - } -}; -CSL.Parallel.prototype.purgeGroupsIfParallel = function (original_condition) { + + +CSL.Parallel.prototype.purgeGroupsIfParallel = function () { for (var i = this.parallel_conditional_blobs_list.length - 1; i > -1; i += -1) { var obj = this.parallel_conditional_blobs_list[i]; - var purgeme = true; - for (var j = 0, jlen = obj.conditions.length; j < jlen; j += 1) { - if (!(!obj.conditions[j] === !!original_condition - || ("master" === obj.conditions[j] - && !this.state.registry.registry[obj.id].master) - || ("servant" === obj.conditions[j] - && !this.state.registry.registry[obj.id].parallel))) { - var purgeme = false; - break; + if (!obj.result && !obj.repeats) { + //this.state.sys.print(i + " No action [render] " + obj.id); + purgeme = false; + } else { + if (obj.condition) { + var purgeme = true; + if (obj.result === obj.condition) { + //this.state.sys.print(i + " Position match [render] "+obj.id); + purgeme = false; + } + //else { + // this.state.sys.print(i + " Position non-match [not-render] "+obj.id + " " + obj.condition + " " + obj.result); + //} + } else if (obj.repeats) { + purgeme = false; + var matches = 0; + //this.state.sys.print(obj.norepeat) + for (var j=0,jlen=obj.norepeat.length; j obj.pos) { @@ -12910,8 +16832,14 @@ CSL.Parallel.prototype.purgeGroupsIfParallel = function (original_condition) { this.parallel_conditional_blobs_list.pop(); } }; + +/*global CSL: true */ + + CSL.Util = {}; + CSL.Util.Match = function () { + this.any = function (token, state, tests) { return function (Item, item) { for (var i=0, ilen=tests.length; i < ilen; i += 1) { @@ -12923,6 +16851,7 @@ CSL.Util.Match = function () { return false; }; }; + this.none = function (token, state, tests) { return function (Item, item) { for (var i=0,ilen=tests.length;i -1) { preferredJurisdiction = "default"; } else if (Item.jurisdiction) { @@ -13003,7 +16999,29 @@ CSL.Transform = function (state) { preferredJurisdiction = "default"; } var jurisdiction = state.transform.loadAbbreviation(preferredJurisdiction, myabbrev_family, normalizedKey, Item.type); + + // Some rules: + // # variable === "country" + // (1) If an abbreviation is associated with the code, then: + // (a) return the abbreviated form if form="short" + // (2) Otherwise: + // (a) return the human-readable country name, or whatever is there if it's not a code + // # variable === "jurisdiction" + // (1) If !!getHumanForm(jurisdictionID, false, false): + // (a) If the code is top-level (i.e. a country): + // (i) return nothing -- this is what the "country" variable is for. + // (b) otherwise: + // (i) If an abbreviation is associated with the code, then: + // (A) return the abbreviated form + // (ii) Otherwise + // (A) return the human-readable form, with the country name & code removed from the front + // (2) Otherwise: + // (a) abbreviate as per normal. + // # other variables + // (1) Abbreviate as per normal. + if (state.transform.abbrevs[jurisdiction][myabbrev_family] && normalizedKey) { + // Safe to test presence of abbrev against raw object in this block var abbrev = state.transform.abbrevs[jurisdiction][myabbrev_family][normalizedKey]; if (tok.strings.form === "short" && abbrev) { if (quashCountry) { @@ -13016,6 +17034,8 @@ CSL.Transform = function (state) { } } } + + // Was for: if (!value && (!state.opt.development_extensions.require_explicit_legal_case_title_short || Item.type !== 'legal_case') && altvar && Item[altvar] && use_field) { @@ -13029,6 +17049,7 @@ CSL.Transform = function (state) { } return value; } + function getFieldLocale(Item,field) { var ret = state.opt["default-locale"][0].slice(0, 2); var localeRex; @@ -13042,6 +17063,7 @@ CSL.Transform = function (state) { if (m) { ret = m[1]; } else { + // Set garbage to "Klingon". ret = "tlh"; } } @@ -13050,14 +17072,18 @@ CSL.Transform = function (state) { } if (!state.opt.development_extensions.strict_text_case_locales || state.opt.development_extensions.normalize_lang_keys_to_lowercase) { + ret = ret.toLowerCase(); } return ret; } + + // Internal functions function getTextSubField (Item, field, locale_type, use_default, stopOrig, family_var) { var opt, o, ret, opts; var usedOrig = stopOrig; var usingOrig = false; + if (!Item[field]) { return { name:"", @@ -13065,9 +17091,14 @@ CSL.Transform = function (state) { token: CSL.Util.cloneToken(this) }; } + // If form="short" is selected ("family_var" is a misnomer + // here, it means short-form requested), and the variable + // has a short-form partner (i.e. it is in array + // VARIABLES_WITH_SHORT_FORM), then it is run here as *-short". var stickyLongForm = false; if (CSL.VARIABLES_WITH_SHORT_FORM.indexOf(field) > -1 && family_var) { + field = field + "-short"; stickyLongForm = true; } @@ -13080,12 +17111,16 @@ CSL.Transform = function (state) { } else { fieldsToTry.push(field); } + for (var h=0,hlen=fieldsToTry.length; h>>/); if (m) { @@ -13226,7 +17284,15 @@ CSL.Transform = function (state) { return value; } this.quashCheck = quashCheck; + + // Return function appropriate to selected options function getOutputFunction(variables, family_var, abbreviation_fallback, alternative_varname) { + // var mytoken; + + // Set the primary_locale and secondary_locale lists appropriately. + // No instance helper function for this; everything can be derived + // from processor settings and rendering context. + var localesets; var langPrefs = CSL.LangPrefsMap[variables[0]]; if (!langPrefs) { @@ -13234,11 +17300,16 @@ CSL.Transform = function (state) { } else { localesets = state.opt['cite-lang-prefs'][langPrefs]; } + return function (state, Item, item) { var primary, primary_locale, secondary, secondary_locale, tertiary, tertiary_locale, primary_tok; if (!variables[0] || (!Item[variables[0]] && !Item[alternative_varname])) { return null; } + // + // Exploring the edges here. + // "suppress-author" for string variables (mostly titles). + // if (!state.tmp.just_looking && item && item["suppress-author"]) { if (!state.tmp.probably_rendered_something && state.tmp.can_substitute.length() > 1) { return null; @@ -13265,18 +17336,25 @@ CSL.Transform = function (state) { slot.primary = 'locale-orig'; } } + if (variables[0] === "title-short" || (state.tmp.area !== "bibliography" && !(state.tmp.area === "citation" && state.opt.xclass === "note" && item && !item.position))) { + slot.secondary = false; slot.tertiary = false; } + if (state.tmp.multi_layout) { slot.secondary = false; slot.tertiary = false; } + + // Problem for multilingual: we really should be + // checking for sanity on the basis of the output + // strings to be actually used. (also below) if (state.tmp["publisher-list"]) { if (variables[0] === "publisher") { state.tmp["publisher-token"] = this; @@ -13285,6 +17363,7 @@ CSL.Transform = function (state) { } return null; } + // True is for transform fallback var res = getTextSubField.call(this, Item, variables[0], slot.primary, true, null, family_var); primary = res.name; primary_locale = res.locale; @@ -13292,13 +17371,18 @@ CSL.Transform = function (state) { var primaryUsedOrig = res.usedOrig; if (family_var && !res.found_variant_ok) { primary = abbreviate(state, primary_tok, Item, alternative_varname, primary, family_var, true); + // Suppress subsequent use of another variable if requested by + // hack syntax in this abbreviation short form. if (primary) { + // The abbreviate() function could use a cleanup, after Zotero correct to use title-short primary = quashCheck(primary); } } if (publisherCheck(this, Item, primary, family_var)) { return null; } + + // No fallback for secondary and tertiary secondary = false; tertiary = false; var secondary_tok; @@ -13310,9 +17394,11 @@ CSL.Transform = function (state) { secondary_tok = res.token; if (family_var && !res.found_variant_ok) { if (secondary) { + // The abbreviate() function could use a cleanup, after Zotero correct to use title-short secondary = abbreviate(state, secondary_tok, Item, false, secondary, family_var, true); } } + //print("XXX secondary_locale: "+secondary_locale); } if (slot.tertiary) { res = getTextSubField.call(this, Item, variables[0], slot.tertiary, false, res.usedOrig, null, family_var); @@ -13321,19 +17407,27 @@ CSL.Transform = function (state) { tertiary_tok = res.token; if (family_var && !res.found_variant_ok) { if (tertiary) { + // The abbreviate() function could use a cleanup, after Zotero correct to use title-short tertiary = abbreviate(state, tertiary_tok, Item, false, tertiary, family_var, true); } } + //print("XXX tertiary_locale: "+tertiary_locale); } + + // Decoration of primary (currently translit only) goes here var primaryPrefix; if (slot.primary === "locale-translit") { primaryPrefix = state.opt.citeAffixes[langPrefs][slot.primary].prefix; } + // XXX This should probably protect against italics at higher + // levels. + if (primaryPrefix === "" && variables[0] === 'title' && !primaryUsedOrig) { var hasItalic = false; for (var i = 0, ilen = primary_tok.decorations.length; i < ilen; i += 1) { if (primary_tok.decorations[i][0] === "@font-style" && primary_tok.decorations[i][1] === "italic") { + hasItalic = true; } } @@ -13341,23 +17435,33 @@ CSL.Transform = function (state) { primary_tok.decorations.push(["@font-style", "italic"]); } } + + //print("XXX "+primary_tok.strings["text-case"]); if (primary_locale !== "en" && primary_tok.strings["text-case"] === "title") { primary_tok.strings["text-case"] = "passthrough"; } + if ("title" === variables[0]) { primary = CSL.demoteNoiseWords(state, primary, this["leading-noise-words"]); } + if (secondary || tertiary) { + state.output.openLevel("empty"); + + // A little too aggressive maybe. primary_tok.strings.suffix = primary_tok.strings.suffix.replace(/[ .,]+$/,""); state.output.append(primary, primary_tok); state.tmp.probably_rendered_something = true; + if (secondary) { secondary_tok.strings.prefix = state.opt.citeAffixes[langPrefs][slot.secondary].prefix; secondary_tok.strings.suffix = state.opt.citeAffixes[langPrefs][slot.secondary].suffix; + // Add a space if empty if (!secondary_tok.strings.prefix) { secondary_tok.strings.prefix = " "; } + // Remove quotes for (var i = secondary_tok.decorations.length - 1; i > -1; i += -1) { if (['@quotes/true', '@font-style/italic', '@font-style/oblique', '@font-weight/bold'].indexOf(secondary_tok.decorations[i].join('/')) > -1) { secondary_tok.decorations = secondary_tok.decorations.slice(0, i).concat(secondary_tok.decorations.slice(i + 1)); @@ -13372,19 +17476,20 @@ CSL.Transform = function (state) { state.output.openLevel(secondary_outer); state.output.append(secondary, secondary_tok); state.output.closeLevel(); + var blob_obj = state.output.current.value(); var blobs_pos = state.output.current.value().blobs.length - 1; - if (state.parallel.use_parallels) { - state.parallel.cite.front.push(variables[0] + ":secondary"); - state.parallel.cite[variables[0] + ":secondary"] = {blobs:[[blob_obj, blobs_pos]]}; - } + // Suppress supplementary multilingual info on subsequent + // partners of a parallel cite? } if (tertiary) { tertiary_tok.strings.prefix = state.opt.citeAffixes[langPrefs][slot.tertiary].prefix; tertiary_tok.strings.suffix = state.opt.citeAffixes[langPrefs][slot.tertiary].suffix; + // Add a space if empty if (!tertiary_tok.strings.prefix) { tertiary_tok.strings.prefix = " "; } + // Remove quotes for (var i = tertiary_tok.decorations.length - 1; i > -1; i += -1) { if (['@quotes/true', '@font-style/italic', '@font-style/oblique', '@font-weight/bold'].indexOf(tertiary_tok.decorations[i].join('/')) > -1) { tertiary_tok.decorations = tertiary_tok.decorations.slice(0, i).concat(tertiary_tok.decorations.slice(i + 1)); @@ -13399,12 +17504,12 @@ CSL.Transform = function (state) { state.output.openLevel(tertiary_outer); state.output.append(tertiary, tertiary_tok); state.output.closeLevel(); + var blob_obj = state.output.current.value(); var blobs_pos = state.output.current.value().blobs.length - 1; - if (state.parallel.use_parallels) { - state.parallel.cite.front.push(variables[0] + ":tertiary"); - state.parallel.cite[variables[0] + ":tertiary"] = {blobs:[[blob_obj, blobs_pos]]}; - } + // Suppress supplementary multilingual info on subsequent + // partners of a parallel cite? + // See note above. } state.output.closeLevel(); } else { @@ -13416,17 +17521,115 @@ CSL.Transform = function (state) { } this.getOutputFunction = getOutputFunction; }; + +/*global CSL: true */ + +/** + * Style token. + *

This 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}.

+ */ + // this.evaluator = false; + /** + * Conditions. + *

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.succeed = false; + /** + * Jump point on failure. + *

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.fail = false; + /** + * Index of next token. + *

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);i-1; i += -1) { if (namelist.slice(i, i+1) === "." && namelist.slice(i+1, i+2) !== " ") { namelist = namelist.slice(0, i) + ". " + namelist.slice(i+1); } } + + // Workaround for Internet Explorer + //namelist = namelist.split(/(\-|\s+)/); + // Workaround for Internet Explorer mm = namelist.match(/[\-\s]+/g); lst = namelist.split(/[\-\s]+/); if (mm === null) { @@ -13808,7 +18171,9 @@ CSL.Util.Names.initializeWith = function (state, name, terminator, normalizeOnly lst[0] += "."; } } + if (lst.length === 0) { + // This doesn't make much sense, and may be impossible. namelist = mm; } else { namelist = [lst[0]]; @@ -13818,6 +18183,8 @@ CSL.Util.Names.initializeWith = function (state, name, terminator, normalizeOnly } } lst = namelist; + + // Use doInitializeName or doNormalizeName, depending on requirements. if (normalizeOnly) { ret = CSL.Util.Names.doNormalize(state, lst, terminator); } else { @@ -13826,9 +18193,12 @@ CSL.Util.Names.initializeWith = function (state, name, terminator, normalizeOnly ret = ret.replace(/\u2013([a-z])/g, "-$1"); return ret; }; + CSL.Util.Names.doNormalize = function (state, namelist, terminator) { var i, ilen; + // namelist is a flat list of given-name elements and space-like separators between them terminator = terminator ? terminator : ""; + // Flag elements that look like abbreviations var isAbbrev = []; for (i = 0, ilen = namelist.length; i < ilen; i += 1) { if (namelist[i].length > 1 && namelist[i].slice(-1) === ".") { @@ -13840,26 +18210,38 @@ CSL.Util.Names.doNormalize = function (state, namelist, terminator) { isAbbrev.push(false); } } + // Step through the elements of the givenname array for (i = 0, ilen = namelist.length; i < ilen; i += 2) { + // If the element is not an abbreviation, leave it and its trailing spaces alone if (isAbbrev[i]) { + // For all elements but the last if (i < namelist.length - 2) { + // Start from scratch on space-like things following an abbreviation namelist[i + 1] = ""; + if (!isAbbrev[i+2]) { namelist[i + 1] = " "; } + + // Add the terminator to the element + // If the following element is not a single-character abbreviation, remove a trailing zero-width non-break space, if present + // These ops may leave some duplicate cruft in the elements and separators. This will be cleaned at the end of the function. if (namelist[i + 2].length > 1) { namelist[i] = namelist[i] + terminator.replace(/\ufeff$/, ""); } else { namelist[i] = namelist[i] + terminator; } } + // For the last element (if it is an abbreviation), just append the terminator if (i === namelist.length - 1) { namelist[i] = namelist[i] + terminator; } } } + // Remove trailing cruft and duplicate spaces, and return return namelist.join("").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020\ufeff\u00a0]+$/,"").replace(/\s*\-\s*/g, "-").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020]+/g, " "); }; + CSL.Util.Names.doInitialize = function (state, namelist, terminator) { var i, ilen, m, j, jlen, lst, n; for (i = 0, ilen = namelist.length; i < ilen; i += 2) { @@ -13892,6 +18274,7 @@ CSL.Util.Names.doInitialize = function (state, namelist, terminator) { extra = s.toLocaleLowerCase(); } } + // namelist[i] = m[1].toLocaleUpperCase() + extra; namelist[i] = m[1] + extra; if (i < (ilen - 1)) { if (terminator.match("%s")) { @@ -13918,6 +18301,7 @@ CSL.Util.Names.doInitialize = function (state, namelist, terminator) { ret = ret.replace(/[\u0009\u000a\u000b\u000c\u000d\u0020\ufeff\u00a0]+$/,"").replace(/\s*\-\s*/g, "-").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020]+/g, " "); return ret; }; + CSL.Util.Names.getRawName = function (name) { var ret = []; if (name.literal) { @@ -13932,8 +18316,33 @@ CSL.Util.Names.getRawName = function (name) { } return ret.join(" "); }; + +// deleted CSL.Util.Names.initNameSlices() +// no longer used. + +// deleted CSL.Util.Names,rescueNameElements() +// apparently not used. + + + +/*global CSL: true */ + +/** + * Date mangling functions. + * @namespace Date construction utilities + */ CSL.Util.Dates = {}; + +/** + * Year manglers + *

short, 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; i0;j--) { if (lst[j-1] && (!lst[j].match(/^[0-9]+([-;,:a-zA-Z]*)$/) || !lst[j-1].match(/^[0-9]+([-;,:a-zA-Z]*)$/))) { lst[j-1] = lst[j-1] + m[j-1] + lst[j]; @@ -14649,6 +19325,7 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { m = m.slice(0,j-1).concat(m.slice(j)); } } + // merge bad leading label into content if (m.length > 0) { var slug = m[0].trim(); var notAlabel = !CSL.STATUTE_SUBDIV_STRINGS[slug] @@ -14664,6 +19341,7 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { origLabel = slug; } } + for (var j=0,jlen=lst.length; j 1 ? m[j-1] : ""; str = lst[j] ? lst[j].trim() : ""; if (j === (lst.length-1)) { values.push(composeNumberInfo(filteredOrigLabel, label, str, elems[i+1])); @@ -14693,13 +19372,16 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } return values; } + function setSpaces(values) { + // Add space joins (is this really right?) for (var i=0,ilen=values.length-1;i -1) { + // Actually, shouldn't we do this always? if (!me.getTerm(CSL.STATUTE_SUBDIV_STRINGS[currentLabelInfo.label])) { values[currentLabelInfo.pos].labelVisibility = true; } } + // If there is an explicit + // label embedded at the start of a field that + // does not match the context, it should be + // marked for rendering. if (["locator", "number", "locator-extra"].indexOf(variable) === -1) { if (CSL.STATUTE_SUBDIV_STRINGS[currentLabelInfo.label] !== variable) { values[0].labelVisibility = true; } } } else { + // Also, mark initial mid-field labels for + // rendering. + //if (values[i-1].label !== values[i].label && currentLabelInfo.label.slice(0, 4) !== "var:") { values[currentLabelInfo.pos].labelVisibility = true; + //} } } } + function setPluralsAndNumerics(values) { if (values.length === 0) { return; } var groupStartPos = 0; var groupCount = 1; + for (var i=1,ilen=values.length;i-1;j--) { + // if (masterNode.decorations[j][0] === "@quotes") { + // // Add to styling + // masterStyling.decorations = masterStyling.decorations.concat(masterNode.decorations.slice(j, j+1)); + // // Remove from node + // masterNode.decorations = masterNode.decorations.slice(0, j).concat(masterNode.decorations.slice(j+1)); + // } + //} masterStyling.strings.prefix = masterNode.strings.prefix; masterNode.strings.prefix = ""; masterStyling.strings.suffix = masterNode.strings.suffix; @@ -14812,6 +19518,7 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { if (values.length) { for (var i=0,ilen=values.length; i -1) { @@ -14846,10 +19554,12 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } return ret; } + function checkPage(variable, val) { return variable === "page" || (["locator", "locator-extra"].indexOf(variable) > -1 && (["p."].indexOf(val.label) > -1 || ["p."].indexOf(val.origLabel) > -1)); } + function fixupRangeDelimiter(variable, val, rangeDelimiter, isNumeric) { var isPage = checkPage(variable, val); var hasTerm = checkTerm(variable, val); @@ -14869,8 +19579,12 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } } } + //if (rangeDelimiter === "\\-") { + // rangeDelimiter = "-"; + //} return rangeDelimiter; } + function manglePageNumbers(values, i, currentInfo) { if (i<1) { return; @@ -14890,18 +19604,21 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { return; } var val = values[i]; + var isPage = checkPage(variable, val); var str; if (isPage && !isNaN(parseInt(values[i-1].value)) && !isNaN(parseInt(values[i].value))) { str = values[i-1].particle + values[i-1].value + " - " + values[i].particle + values[i].value; str = me.fun.page_mangler(str); } else { + // if (("" + values[i-1].value).match(/[0-9]$/) && ("" + values[i].value).match(/^[0-9]/)) { if (("" + values[i-1].value).match(/^([0-9]+|[ivxlcmIVXLCM]+)$/) && ("" + values[i].value).match(/^([0-9]+|[ivxlcmIVXLCM]+)$/)) { values[i-1].joiningSuffix = me.getTerm("page-range-delimiter"); } str = values[i-1].value + stripHyphenBackslash(values[i-1].joiningSuffix) + values[i].value; } var m = str.match(/^((?:[0-9]*[a-zA-Z]+0*))?([0-9]+[a-z]*)(\s*[^0-9]+\s*)([-,a-zA-Z]?0*)([0-9]+[a-z]*)$/); + // var m = str.match(/^((?:[0-9]*[a-zA-Z]+0*))?([0-9]+[a-z]*)(\s*[^0-9]+\s*)([-,a-zA-Z]?0*)([0-9]+[a-z]*)$/); if (m) { var rangeDelimiter = m[3]; rangeDelimiter = fixupRangeDelimiter(variable, val, rangeDelimiter, values[i].numeric); @@ -14913,18 +19630,22 @@ CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) { } currentInfo.count = 0; } + function fixRanges(values) { + if (!node) { return; } if (["page", "page-first", "chapter-number", "collection-number", "edition", "issue", "number", "number-of-pages", "number-of-volumes", "volume", "locator", "locator-extra"].indexOf(variable) === -1) { return; } + var currentInfo = { count: 0, label: null, lastHadRangeDelimiter: false }; + for (var i=0,ilen=values.length; i": { type: "nocase", @@ -15360,22 +20190,29 @@ CSL.Util.FlipFlopper = function(state) { } } }; + _nestingData["(\""] = _nestingData[" \""]; _nestingData["(\'"] = _nestingData[" \'"]; + var localeOpenQuote = state.getTerm("open-quote"); var localeCloseQuote = state.getTerm("close-quote"); var localeOpenInnerQuote = state.getTerm("open-inner-quote"); var localeCloseInnerQuote = state.getTerm("close-inner-quote"); + + // If locale uses straight quotes, do not register them. All will be well. + // Otherwise, clone straight-quote data, and adjust. if (localeOpenQuote && localeCloseQuote && [" \""," \'","\"","\'"].indexOf(localeOpenQuote) === -1) { _nestingData[localeOpenQuote] = JSON.parse(JSON.stringify(_nestingData[" \""])); _nestingData[localeOpenQuote].opener = localeOpenQuote; _nestingData[localeOpenQuote].closer = localeCloseQuote; } + if (localeOpenInnerQuote && localeCloseInnerQuote && [" \""," \'","\"","\'"].indexOf(localeOpenInnerQuote) === -1) { _nestingData[localeOpenInnerQuote] = JSON.parse(JSON.stringify(_nestingData[" \'"])); _nestingData[localeOpenInnerQuote].opener = localeOpenInnerQuote; _nestingData[localeOpenInnerQuote].closer = localeCloseInnerQuote; } + function _setOuterQuoteForm(quot) { var flip = { " \'": " \"", @@ -15386,6 +20223,7 @@ CSL.Util.FlipFlopper = function(state) { _nestingData[quot].outer = "true"; _nestingData[flip[quot]].outer = "inner"; } + function _getNestingOpenerParams(opener) { var openers = []; var keys = Object.keys(_nestingData); @@ -15401,6 +20239,7 @@ CSL.Util.FlipFlopper = function(state) { }).join("|") + ")"); return ret; } + var _nestingParams = (function() { var ret = {}; var keys = Object.keys(_nestingData); @@ -15410,6 +20249,7 @@ CSL.Util.FlipFlopper = function(state) { } return ret; }()); + var _tagRex = (function() { var openers = []; var closers = []; @@ -15423,9 +20263,11 @@ CSL.Util.FlipFlopper = function(state) { var closer = keys[i]; closers.push(closer); } + var all = openers.concat(closers).map(function(str){ return str.replace("(", "\\("); }).join("|"); + return { matchAll: new RegExp("((?:" + all + "))", "g"), splitAll: new RegExp("(?:" + all + ")", "g"), @@ -15435,6 +20277,7 @@ CSL.Util.FlipFlopper = function(state) { close: new RegExp("(^(?:" + closers.join("|") + ")$)"), }; }()); + function _tryOpen(tag, pos) { var params = _nestingState[_nestingState.length - 1]; if (!params || tag.match(params.opener)) { @@ -15458,6 +20301,7 @@ CSL.Util.FlipFlopper = function(state) { }; } } + function _tryClose(tag, pos) { var params = _nestingState[_nestingState.length - 1]; if (params && tag === params.closer) { @@ -15484,6 +20328,7 @@ CSL.Util.FlipFlopper = function(state) { } } } + function _pushNestingState(tag, pos) { if (tag.match(_tagRex.open)) { return _tryOpen(tag, pos); @@ -15491,13 +20336,17 @@ CSL.Util.FlipFlopper = function(state) { return _tryClose(tag, pos); } } + function _nestingFix (tag, pos) { return _pushNestingState(tag, pos); } + function _doppelString(str) { var forcedSpaces = []; + // Normalize markup str = str.replace(/(]*(>)/g, "$1 $2$3;\"$4"); str = str.replace(/(]*(>)/g, "$1 $2$3"); + var match = str.match(_tagRex.matchAll); if (!match) { return { @@ -15507,6 +20356,7 @@ CSL.Util.FlipFlopper = function(state) { }; } var split = str.split(_tagRex.splitAll); + for (var i=0,ilen=match.length-1;i -1) { @@ -15523,6 +20373,7 @@ CSL.Util.FlipFlopper = function(state) { forcedSpaces: forcedSpaces }; } + var TagReg = function(blob) { var _stack = []; this.set = function (tag) { @@ -15566,6 +20417,7 @@ CSL.Util.FlipFlopper = function(state) { _stack.pop(); }; }; + function _apostropheForce(tag, str) { if (tag === "\'") { if (str && str.match(/^[^\,\.\?\:\;\ ]/)) { @@ -15576,6 +20428,7 @@ CSL.Util.FlipFlopper = function(state) { } return false; } + function _undoppelToQueue(blob, doppel, leadingSpace) { var firstString = true; var tagReg = new TagReg(blob); @@ -15604,6 +20457,11 @@ CSL.Util.FlipFlopper = function(state) { var tok = new CSL.Token(); var newblob = new CSL.Blob(null, tok); newblob.alldecor = this.latest.alldecor.slice(); + + // AHA! Bad naming. There is _decorset from the list, and + // there WAS decorset that we are building. Dumb. Fix the + // names and fix it up. + if (decor[0] === "@class" && decor[1] === "nodecor") { var newdecorset = []; var seen = {}; @@ -15617,6 +20475,7 @@ CSL.Util.FlipFlopper = function(state) { var _olddecor = _decorset[j]; if (["@font-weight", "@font-style", "@font-variant"].indexOf(_olddecor[0]) > -1 && !seen[_olddecor[0]]) { + if (decor[1] !== "normal") { newblob.decorations.push([_olddecor[0], "normal"]); newdecorset.push([_olddecor[0], "normal"]); @@ -15626,6 +20485,7 @@ CSL.Util.FlipFlopper = function(state) { } } newblob.alldecor.push(newdecorset); + } else { newblob.decorations.push(decor); newblob.alldecor.push([decor]); @@ -15674,6 +20534,11 @@ CSL.Util.FlipFlopper = function(state) { } } } + + /** + * PUBLIC + */ + this.processTags = function (blob) { var str = blob.blobs; var leadingSpace = false; @@ -15687,6 +20552,11 @@ CSL.Util.FlipFlopper = function(state) { return; } var quoteFormSeen = false; + // ZZZ + // It is inside THIS loop that we can convert the nocase and nodecor + // tags and companion spans to string + // Um. Maybe. Or maybe it needs to happen inside _nestingFix() somewhere. + for (var i=0,ilen=doppel.tags.length;i -1) { if (tag.match(_tagRex.close) && tag === "\'") { + doppel.strings[i+1] = "\u2019" + doppel.strings[i+1]; doppel.tags[i] = ""; } else { @@ -15737,10 +20608,12 @@ CSL.Util.FlipFlopper = function(state) { } } } + // Stray tags are neutralized here for (var i=_nestingState.length-1;i>-1;i--) { var tagPos = _nestingState[i].pos; var tag = doppel.tags[tagPos]; if (tag === " \'" || tag === "\'") { + doppel.strings[tagPos+1] = " \u2019" + doppel.strings[tagPos+1]; } else { doppel.strings[tagPos+1] = doppel.tags[tagPos] + doppel.strings[tagPos+1]; @@ -15755,6 +20628,8 @@ CSL.Util.FlipFlopper = function(state) { doppel.strings = doppel.strings.slice(0,i+1).concat(doppel.strings.slice(i+2)); } } + // Sniff initial (outer) quote form (single or double) and configure parser + // Also add leading spaces. for (var i=0,ilen=doppel.tags.length;i||<\/span>|<\/?(?:i|sc|b|sub|sup)>)"; var tagDoppel = new CSL.Doppeler(rexStr, function(str) { return str.replace(/(]*(>)/g, "$1 $2$3").replace(/(]*(>)/g, "$1 $2 $3;$4$5"); }); + var wordDoppel = new CSL.Doppeler("(?:[\u0020\u00A0\u2000-\u200B\u205F\u3000]+)"); + + /** + * INTERNAL + */ + var _tagParams = { "": "", "": "", @@ -15785,13 +20671,19 @@ CSL.Output.Formatters = (function () { "": "", "": "" }; + function _capitalise (word) { + // Weird stuff is (.) transpiled with regexpu + // https://github.com/mathiasbynens/regexpu var m = word.match(/(^\s*)((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))(.*)/); + // Do not uppercase lone Greek letters + // (No case transforms in Greek citations, but chars used in titles to science papers) if (m && !(m[2].match(/^[\u0370-\u03FF]$/) && !m[3])) { return m[1] + m[2].toUpperCase() + m[3]; } return word; } + function _textcaseEngine(config, string) { if (!string) { return ""; @@ -15855,30 +20747,41 @@ CSL.Output.Formatters = (function () { return pushQuoteState(m[1], positions); } } + // Run state machine if (config.doppel.strings.length && config.doppel.strings[0].trim()) { config.doppel.strings[0] = config.capitaliseWords(config.doppel.strings[0], 0, config.doppel.tags[0]); } + for (var i=0,ilen=config.doppel.tags.length;icapitalize_first, but force the + * subsequent characters to lowercase. + */ function sentence(state, string) { var config = { quoteState: [], @@ -15985,6 +20916,7 @@ CSL.Output.Formatters = (function () { }; return _textcaseEngine(config, string); } + function title(state, string) { var config = { quoteState: [], @@ -15999,12 +20931,15 @@ CSL.Output.Formatters = (function () { continue; } if (word.length > 1 && !word.toLowerCase().match(config.skipWordsRex)) { + // Capitalize every word that is not a stop-word words[j] = _capitalise(words[j]); } else if (j === (words.length - 1) && followingTag === "-") { words[j] = _capitalise(words[j]); } else if (config.isFirst) { + // Capitalize first word, even if a stop-word words[j] = _capitalise(words[j]); } else if (config.afterPunct) { + // Capitalize after punctuation words[j] = _capitalise(words[j]); } config.afterPunct = false; @@ -16025,6 +20960,12 @@ CSL.Output.Formatters = (function () { }; return _textcaseEngine(config, string); } + + + /** + * Force capitalization of the first letter in the string, leave + * the rest of the characters untouched. + */ function capitalizeFirst(state, string) { var config = { quoteState: [], @@ -16049,6 +20990,13 @@ CSL.Output.Formatters = (function () { }; return _textcaseEngine(config, string); } + + /** + * Force the first letter of each space-delimited + * word in the string to uppercase, and leave the remainder + * of the string untouched. Single characters are forced + * to uppercase. + */ function capitalizeAll (state, string) { var config = { quoteState: [], @@ -16079,9 +21027,33 @@ CSL.Output.Formatters = (function () { "capitalize-all": capitalizeAll }; }()); + +/*global CSL: true */ + + +/** + * Output specifications. + * @class + */ CSL.Output.Formats = function () {}; + +/** + * HTML output format specification. + *

The 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 "
\n"+str+"
"; + //}, "@cite/entry": function (state, str) { return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt); }, "@bibliography/entry": function (state, str) { + // Test for this.item_id to add decorations to + // bibliography output of individual entries. + // + // Full item content can be obtained from + // state.registry.registry[id].ref, using + // CSL variable keys. + // + // Example: + // + // print(state.registry.registry[this.item_id].ref["title"]); + // + // At present, for parallel citations, only the + // id of the master item is supplied on this.item_id. var insert = ""; if (state.sys.embedBibliographyEntry) { insert = state.sys.embedBibliographyEntry(this.item_id) + "\n"; @@ -16182,7 +21174,20 @@ CSL.Output.Formats.prototype.html = { return "" + str + ""; } }; + +/** + * Plain text output specification. + * + * (Code contributed by Simon Kornblith, Center for History and New Media, + * George Mason University.) + */ CSL.Output.Formats.prototype.text = { + // + // 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) { if (!text) { text = ""; @@ -16215,11 +21220,17 @@ CSL.Output.Formats.prototype.text = { }, "@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 "
\n"+str+"
"; + //}, "@cite/entry": function (state, str) { return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt); }, @@ -16248,7 +21259,20 @@ CSL.Output.Formats.prototype.text = { return str; } }; + +/** + * Plain text output specification. + * + * (Code contributed by Simon Kornblith, Center for History and New Media, + * George Mason University.) + */ CSL.Output.Formats.prototype.rtf = { + // + // 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) { if (!text) { text = ""; @@ -16296,6 +21320,8 @@ CSL.Output.Formats.prototype.rtf = { "bibend":"}", "@display/block": "\\line{}%%STRING%%\\line\r\n", "@cite/entry": function (state, str) { + // If wrapCitationEntry does not exist, cite/entry + // is not applied. return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt); }, "@bibliography/entry": function(state,str){ @@ -16335,6 +21361,26 @@ CSL.Output.Formats.prototype.rtf = { return str; } }; + +/* + + This does not seem to work in Zotero plugins. For some reason the scope of the link does not + close when interpreted by the LibreOffice. Perhaps this creates a field within a field, + and that is not allowed? + + "@URL/true": function (state, str) { + return "\\field{\\*\\fldinst{HYPERLINK \"" + str + "\"}}{\\fldrslt{"+ str +"}}"; + }, + "@DOI/true": function (state, str) { + return "\\field{\\*\\fldinst{HYPERLINK \"https://doi.org/" + str + "\"}}{\\fldrslt{"+ str +"}}"; + } +*/ + +/** + * AsciiDoc output specification. + * + * See http://asciidoc.org/ or https://asciidoctor.org/ + */ CSL.Output.Formats.prototype.asciidoc = { "text_escape": function (text) { if (!text) { @@ -16383,6 +21429,7 @@ CSL.Output.Formats.prototype.asciidoc = { }, "@quotes/false": false, "@cite/entry": function (state, str) { + // if wrapCitationEntry does not exist, cite/entry is not applied return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt); }, "@bibliography/entry": function (state, str) { @@ -16419,6 +21466,7 @@ CSL.Output.Formats.prototype.asciidoc = { } }, "@URL/true": function (state, str) { + // AsciiDoc renders URLs automatically as links return str; }, "@DOI/true": function (state, str) { @@ -16429,6 +21477,13 @@ CSL.Output.Formats.prototype.asciidoc = { return doiurl + "[" + str + "]"; } }; + +/** + * Output specification for XSL-FO (Extensible Stylesheet + * Language - Formatting Objects) + * + * See https://www.w3.org/TR/xsl11/#fo-section + */ CSL.Output.Formats.prototype.fo = { "text_escape": function (text) { if (!text) { @@ -16541,6 +21596,12 @@ CSL.Output.Formats.prototype.fo = { return "" + str + ""; } }; + +/** + * LaTeX .bbl output. + * + * (Code contributed by Egon Willighagen, based on the prototype.text code.) + */ CSL.Output.Formats.prototype.latex = { "text_escape": function (text) { if (!text) { @@ -16574,11 +21635,17 @@ CSL.Output.Formats.prototype.latex = { }, "@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 "
\n"+str+"
"; + //}, "@cite/entry": function (state, str) { return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt); }, @@ -16607,7 +21674,67 @@ CSL.Output.Formats.prototype.latex = { return str; } }; + CSL.Output.Formats = new CSL.Output.Formats(); + +/*global CSL: true */ + + +// +// Time for a rewrite of this module. +// +// Simon has pointed out that list and hash behavior can +// be obtained by ... just using a list and a hash. This +// is faster for batched operations, because sorting is +// greatly optimized. Since most of the interaction +// with plugins at runtime will involve batches of +// references, there will be solid gains if the current, +// one-reference-at-a-time approach implemented here +// can be replaced with something that leverages the native +// sort method of the Array() type. +// +// That's going to take some redesign, but it will simplify +// things in the long run, so it might as well happen now. +// +// We'll keep makeCitationCluster and makeBibliography as +// simple methods that return a string. Neither should +// have any effect on internal state. This will be a change +// in behavior for makeCitationCluster. +// +// A new updateItems command will be introduced, to replace +// insertItems. It will be a simple list of IDs, in the +// sequence of first reference in the document. +// +// The calling application should always invoke updateItems +// before makeCitationCluster. +// + +// +// should allow batched registration of items by +// key. should behave as an update, with deletion +// of items and the tainting of disambiguation +// partner sets affected by a deletes and additions. +// +// +// we'll need a reset method, to clear the decks +// in the citation area and start over. + +/** + * Registry of cited items. + *

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.

+ * @class + */ CSL.Registry = function (state) { this.debug = false; this.state = state; @@ -16616,7 +21743,12 @@ CSL.Registry = function (state) { this.refhash = {}; this.namereg = new CSL.Registry.NameReg(state); this.citationreg = new CSL.Registry.CitationReg(state); + // See CSL.NameOutput.prototype.outputNames + // and CSL.Registry.prototype.doinserts this.authorstrings = {}; + + // + // shared scratch vars this.mylist = []; this.myhash = {}; this.deletes = []; @@ -16626,9 +21758,15 @@ CSL.Registry = function (state) { this.akeys = {}; this.oldseq = {}; this.return_data = {}; + // + // each ambig is a list of the ids of other objects + // that have the same base-level rendering this.ambigcites = {}; this.ambigresets = {}; this.sorter = new CSL.Registry.Comparifier(state, "bibliography_sort"); + //this.modes = CSL.getModes.call(this.state); + //this.checkerator = new CSL.Checkerator(); + this.getSortedIds = function () { var ret = []; for (var i = 0, ilen = this.reflist.length; i < ilen; i += 1) { @@ -16636,6 +21774,7 @@ CSL.Registry = function (state) { } return ret; }; + this.getSortedRegistryItems = function () { var ret = []; for (var i = 0, ilen = this.reflist.length; i < ilen; i += 1) { @@ -16644,10 +21783,79 @@ CSL.Registry = function (state) { return ret; }; }; + +// +// Here's the sequence of operations to be performed on +// update: +// +// 1. (o) [init] Receive list as function argument, store as hash and as list. +// 2. (o) [init] Initialize refresh list. Never needs sorting, only hash required. + +// 3. (o) [dodeletes] Delete loop. +// 3a. (o) [dodeletes] Delete names in items to be deleted from names reg. +// 3b. (o) [dodeletes] Complement refreshes list with items affected by +// possible name changes. We'll actually perform the refresh once +// all of the necessary data and parameters have been established +// in the registry. +// 3c. (o) [dodeletes] Delete all items to be deleted from their disambig pools. +// 3d. (o) [dodeletes] Delete all items in deletion list from hash. + +// 4. (o) [doinserts] Insert loop. +// 4a. (o) [doinserts] Retrieve entries for items to insert. +// 4b. (o) [doinserts] Generate ambig key. +// 4c. (o) [doinserts] Add names in items to be inserted to names reg +// (implicit in getAmbiguousCite). +// 4d. (o) [doinserts] Record ambig pool key on akey list (used for updating further +// down the chain). +// 4e. (o) [doinserts] Create registry token. +// 4f. (o) [doinserts] Add item ID to hash. +// 4g. (o) [doinserts] Set and record the base token to hold disambiguation +// results ("disambig" in the object above). +// 5. (o) [rebuildlist] Create "new" list of hash pointers, in the order given +// in the argument to the update function. +// 6. (o) [rebuildlist] Apply citation numbers to new list. +// 7. (o) [dorefreshes] Refresh items requiring update. + + + +// 5. (o) [delnames] Delete names in items to be deleted from names reg, and obtain IDs +// of other items that would be affected by changes around that surname. +// 6. (o) [delnames] Complement delete and insert lists with items affected by +// possible name changes. +// 7. (o) [delambigs] Delete all items to be deleted from their disambig pools. +// 8. (o) [delhash] Delete all items in deletion list from hash. + +// 9. (o) [addtohash] Retrieve entries for items to insert. +// 10. (o) [addtohash] Add items to be inserted to their disambig pools. +// 11. (o) [addtohash] Add names in items to be inserted to names reg +// (implicit in getAmbiguousCite). +// 12. (o) [addtohash] Create registry token for each item to be inserted. +// 13. (o) [addtohash] Add items for insert to hash. + +// 14. (o) [buildlist] Create "new" list of hash pointers, in the order given in the argument +// to the update function. +// 15. (o) [renumber] Apply citation numbers to new list. +// 16. (o) [setdisambigs] Set disambiguation parameters on each inserted item token. +// 17. (o) [setsortkeys] Set sort keys on each item token. +// 18. (o) [sorttokens] Resort token list +// 19. (o) [renumber] Reset citation numbers on list items +// + CSL.Registry.prototype.init = function (itemIDs, uncited_flag) { var i, ilen; this.oldseq = {}; + // 1. Receive list as function argument, store as hash and as list. + // + // Result: + // this.mylist: a list of all itemIDs of referenced items, cited and uncited. + // this.myhash: a hash of index positions in this.mylist. + // this.uncited: hash of uncited itemIDs. + // + // Proceed as follows. + // if (uncited_flag) { + // If uncited_flag is non-nil, add any missing itemIDs to this.mylist + // from itemIDs input list, and set the itemIDs in itemIDs on this.uncited. this.uncited = {}; for (var i=0,ilen=itemIDs.length;i-1;i+=-1) { + // Might not want to be quite this restrictive. if (this.citationreg.citationsByItemId) { if (!this.citationreg.citationsByItemId[this.mylist[i]] && !myhash[this.mylist[i]]) { delete this.myhash[this.mylist[i]]; @@ -16687,6 +21904,7 @@ CSL.Registry.prototype.dopurge = function (myhash) { } this.dodeletes(this.myhash); }; + CSL.Registry.prototype.dodeletes = function (myhash) { var otheritems, key, ambig, pos, len, items, kkey, mypos, id; if ("string" === typeof myhash) { @@ -16694,15 +21912,31 @@ CSL.Registry.prototype.dodeletes = function (myhash) { myhash = {}; myhash[key] = true; } + // + // 3. Delete loop. + // for (var key in this.registry) { if (!myhash[key]) { + // skip items explicitly marked as uncited if (this.uncited[key]) { continue; } + // + // 3a. Delete names in items to be deleted from names reg. + // otheritems = this.namereg.delitems(key); + // + // 3b. Complement refreshes list with items affected by + // possible name changes. We'll actually perform the refresh once + // all of the necessary data and parameters have been established + // in the registry. + // for (kkey in otheritems) { this.refreshes[kkey] = true; } + // + // 3c. Delete all items to be deleted from their disambig pools. + // ambig = this.registry[key].ambig; mypos = this.ambigcites[ambig].indexOf(key); if (mypos > -1) { @@ -16710,11 +21944,21 @@ CSL.Registry.prototype.dodeletes = function (myhash) { this.ambigcites[ambig] = items.slice(0, mypos).concat(items.slice(mypos+1, items.length)); this.ambigresets[ambig] = this.ambigcites[ambig].length; } + // + // XX. What we've missed is to provide an update of all + // items sharing the same ambig += -1 the remaining items in + // ambigcites. So let's do that here, just in case the + // names update above doesn't catch them all. + // len = this.ambigcites[ambig].length; for (pos = 0; pos < len; pos += 1) { id = "" + this.ambigcites[ambig][pos]; this.refreshes[id] = true; } + // + // 3d-0. Remove parallel id references and realign + // parallel ID refs. + // if (this.registry[key].siblings) { if (this.registry[key].siblings.length == 1) { var loneSiblingID = this.registry[key].siblings[0]; @@ -16745,31 +21989,63 @@ CSL.Registry.prototype.dodeletes = function (myhash) { } } } + // + // 3d-1. Remove item from reflist for (var i=this.reflist.length-1;i>-1;i--) { if (this.reflist[i].id === key) { this.reflist = this.reflist.slice(0, i).concat(this.reflist.slice(i+1)); } } + // + // 3d. Delete all items in deletion list from hash. + // delete this.registry[key]; delete this.refhash[key]; + + // For processCitationCluster() this.return_data.bibchange = true; } } + // Disabled. See formats.js for code. + // this.state.fun.decorate.items_delete( this.state.output[this.state.opt.mode].tmp, myhash ); }; + CSL.Registry.prototype.doinserts = function (mylist) { var item, Item, akey, newitem, abase, i, ilen; if ("string" === typeof mylist) { mylist = [mylist]; } + // + // 4. Insert loop. + // for (var i = 0, ilen = mylist.length; i < ilen; i += 1) { item = mylist[i]; if (!this.registry[item]) { + // + // 4a. Retrieve entries for items to insert. + // Item = this.state.retrieveItem(item); + + // + // 4b. Generate ambig key. + // + // AND + // + // 4c. Add names in items to be inserted to names reg + // (implicit in getAmbiguousCite). + // akey = CSL.getAmbiguousCite.call(this.state, Item); this.ambigsTouched[akey] = true; + // + // 4d. Record ambig pool key on akey list (used for updating further + // down the chain). + // if (!Item.legislation_id) { this.akeys[akey] = true; } + // + // 4e. Create registry token. + // newitem = { "id": "" + item, "seq": 0, @@ -16781,21 +22057,76 @@ CSL.Registry.prototype.doinserts = function (mylist) { "ref": Item, "newItem": true }; + // + // + // 4f. Add item ID to hash. + // this.registry[item] = newitem; + // + // 4f(a). Add first reference note number + // (this may be redundant) if (this.citationreg.citationsByItemId && this.citationreg.citationsByItemId[item]) { this.registry[item]["first-reference-note-number"] = this.citationreg.citationsByItemId[item][0].properties.noteIndex; } + + // + // 4g. Set and record the base token to hold disambiguation + // results ("disambig" in the object above). + // abase = CSL.getAmbigConfig.call(this.state); this.registerAmbigToken(akey, item, abase); + + //if (!this.ambigcites[akey]){ + // this.ambigcites[akey] = []; + //} + //CSL.debug("Run: "+item+"("+this.ambigcites[akey]+")"); + //if (this.ambigcites[akey].indexOf(item) === -1){ + // CSL.debug(" Add: "+item); + // this.ambigcites[akey].push(item); + //} + // + // 4h. Make a note that this item needs its sort keys refreshed. + // this.touched[item] = true; + // For processCitationCluster() this.return_data.bibchange = true; } } + // Disabled. See formats.js for code. + // this.state.fun.decorate.items_add( this.state.output[this.state.opt.mode].tmp, mylist ); }; + +/* +// No longer required. +CSL.Registry.prototype.douncited = function () { + var pos, len; + var cited_len = this.mylist.length - this.uncited.length; + for (pos = 0, len = cited_len; pos < len; pos += 1) { + this.registry[this.mylist[pos]].uncited = false; + } + for (pos = cited_len, len = this.mylist.length; pos < len; pos += 1) { + this.registry[this.mylist[pos]].uncited = true; + } +}; +*/ + CSL.Registry.prototype.rebuildlist = function (nosort) { var len, pos, item, Item; + // + // 5. Create "new" list of hash pointers, in the order given in the argument + // to the update function. + // + // + // XXX Keep reflist in place. + // if (!nosort) { this.reflist_inserts = []; + // + // 6. Apply citation numbers to new list, + // saving off old sequence numbers as we go. + // + // XXX Just memo inserts -- actual insert happens below, at last "sort" + // len = this.mylist.length; for (pos = 0; pos < len; pos += 1) { item = this.mylist[pos]; @@ -16818,8 +22149,25 @@ CSL.Registry.prototype.rebuildlist = function (nosort) { } } }; + +/* + * Okay, at this point we should have a numbered list + * of registry tokens in the notional order requested, + * with sequence numbers to reconstruct the ordering + * if the list is remangled. So far so good. + */ + CSL.Registry.prototype.dorefreshes = function () { var key, regtoken, Item, akey, abase; + // + // 7. Refresh items requiring update. + // + // It looks like we need to do four things on each cite for refresh: + // (1) Generate the akey for the cite. + // (2) Register it on the ambig token. + // (3) Register the akey in this.akeys + // (4) Register the item ID in this.touched + // for (var key in this.refreshes) { regtoken = this.registry[key]; if (!regtoken) { @@ -16828,6 +22176,7 @@ CSL.Registry.prototype.dorefreshes = function () { regtoken.sortkeys = undefined; Item = this.state.refetchItem(key); var akey = regtoken.ambig; + if ("undefined" === typeof akey) { this.state.tmp.disambig_settings = false; akey = CSL.getAmbiguousCite.call(this.state, Item); @@ -16853,25 +22202,56 @@ CSL.Registry.prototype.dorefreshes = function () { this.touched[key] = true; } }; + +/* + * Main disambiguation += -1 can everything for disambiguation be + * crunched into this function? + */ CSL.Registry.prototype.setdisambigs = function () { + // + // Okay, more changes. Here is where we resolve all disambiguation + // issues for cites touched by the update. The this.ambigcites set is + // based on the complete short form of citations, and is the basis on + // which names are added and minimal adding of initials or given names + // is performed. + // + + // + // 8. Set disambiguation parameters on each inserted item token. + // for (var akey in this.ambigsTouched) { + // + // Disambiguation is fully encapsulated. + // Disambiguator will run only if there are multiple + // items, and at least one disambiguation mode is + // in effect. this.state.disambiguate.run(akey); } this.ambigsTouched = {}; this.akeys = {}; }; + + + CSL.Registry.prototype.renumber = function () { var len, pos, item; + // + // 19. Reset citation numbers on list items + // if (this.state.bibliography_sort.opt.citation_number_sort_direction === CSL.DESCENDING) { this.state.bibliography_sort.tmp.citation_number_map = {}; } len = this.reflist.length; for (pos = 0; pos < len; pos += 1) { item = this.reflist[pos]; + // save the overhead of rerenderings if citation-number is not + // used in the style. item.seq = (pos + 1); if (this.state.bibliography_sort.opt.citation_number_sort_direction === CSL.DESCENDING) { this.state.bibliography_sort.tmp.citation_number_map[item.seq] = (this.reflist.length - item.seq + 1); } + // update_mode is set to CSL.NUMERIC if citation-number is rendered + // in citations. if (this.state.opt.update_mode === CSL.NUMERIC && item.seq != this.oldseq[item.id]) { this.state.tmp.taintedItemIDs[item.id] = true; } @@ -16880,19 +22260,26 @@ CSL.Registry.prototype.renumber = function () { } } }; + CSL.Registry.prototype.setsortkeys = function () { var key; + // + // 17. Set sort keys on each item token. + // for (var i = 0, ilen = this.mylist.length; i < ilen; i += 1) { var key = this.mylist[i]; + // The last of these conditions may create some thrashing on styles that do not require sorting. if (this.touched[key] || this.state.tmp.taintedItemIDs[key] || !this.registry[key].sortkeys) { this.registry[key].sortkeys = CSL.getSortKeys.call(this.state, this.state.retrieveItem(key), "bibliography_sort"); } } }; + CSL.Registry.prototype._insertItem = function(element, array) { array.splice(this._locationOf(element, array) + 1, 0, element); return array; }; + CSL.Registry.prototype._locationOf = function(element, array, start, end) { if (array.length === 0) { return -1; @@ -16900,6 +22287,7 @@ CSL.Registry.prototype._locationOf = function(element, array, start, end) { start = start || 0; end = end || array.length; var pivot = (start + end) >> 1; // should be faster than dividing by 2 + var c = this.sorter.compareKeys(element, array[pivot]); if (end - start <= 1) { return c == -1 ? pivot - 1 : pivot; @@ -16910,8 +22298,12 @@ CSL.Registry.prototype._locationOf = function(element, array, start, end) { case 1: return this._locationOf(element, array, pivot, end); } }; + CSL.Registry.prototype.sorttokens = function (nosort) { var len, item, Item, pos; + // + // 18. Resort token list. + // if (!nosort) { this.reflist_inserts = []; len = this.mylist.length; @@ -16922,8 +22314,11 @@ CSL.Registry.prototype.sorttokens = function (nosort) { this.reflist_inserts.push(Item); } } + // There is a thin possibility that tainted items in a sorted list + // will change position due to disambiguation. We cover for that here. for (var key in this.state.tmp.taintedItemIDs) { if (this.registry[key] && !this.registry[key].newItem) { + // Move tainted items from reflist to reflist_inserts for (var i=this.reflist.length-1;i>-1;i--) { if (this.reflist[i].id === key) { this.reflist_inserts.push(this.reflist[i]); @@ -16944,6 +22339,11 @@ CSL.Registry.prototype.sorttokens = function (nosort) { } } }; + +/** + * Compare two sort keys + *

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;jThis is used internally by the Registry.

+ */ CSL.getSortKeys = function (Item, key_type) { var area, root, extension, strip_prepositions, len, pos; + //SNIP-START + if (false) { + CSL.debug("KEY TYPE: " + key_type); + } + //SNIP-END area = this.tmp.area; root = this.tmp.root; extension = this.tmp.extension; strip_prepositions = CSL.Util.Sort.strip_prepositions; this.tmp.area = key_type; + // Gawdawful, this. this.tmp.root = key_type.indexOf("_") > -1 ? key_type.slice(0,-5) : key_type; this.tmp.extension = "_sort"; this.tmp.disambig_override = true; @@ -17035,38 +22473,61 @@ CSL.getSortKeys = function (Item, key_type) { for (pos = 0; pos < len; pos += 1) { this[key_type].keys[pos] = strip_prepositions(this[key_type].keys[pos]); } + //SNIP-START + if (false) { + CSL.debug("sort keys (" + key_type + "): " + this[key_type].keys); + } + //SNIP-END + this.tmp.area = area; this.tmp.root = root; this.tmp.extension = extension; return this[key_type].keys; }; + + +/*global CSL: true */ + CSL.Registry.NameReg = function (state) { var pkey, ikey, skey, dagopt, gdropt, items, strip_periods, set_keys, evalname, delitems, addname, myitems; this.state = state; this.namereg = {}; this.nameind = {}; + // used for restoring state following preview this.nameindpkeys = {}; + // + // family, initials form, fullname (with given stripped of periods) + // + // keys registered, indexed by ID this.itemkeyreg = {}; + strip_periods = function (str) { if (!str) { str = ""; } return str.replace(/\./g, " ").replace(/\s+/g, " ").replace(/\s+$/,""); }; + set_keys = function (state, itemid, nameobj) { pkey = strip_periods(nameobj.family); skey = strip_periods(nameobj.given); + // Drop lowercase suffixes (such as et al.) from given name field + // for disambiguation purposes. var m = skey.match(/[,\!]* ([^,]+)$/); if (m && m[1] === m[1].toLowerCase()) { skey = skey.replace(/[,\!]* [^,]+$/, ""); } + // The %s terminator enables normal initialization behavior + // with non-Byzantine names. ikey = CSL.Util.Names.initializeWith(state, skey, "%s"); if (state.citation.opt["givenname-disambiguation-rule"] === "by-cite") { pkey = "" + itemid + pkey; } }; + evalname = function (item_id, nameobj, namenum, request_base, form, initials) { var param; + // XXX THIS CAN NO LONGER HAPPEN if (state.tmp.area.slice(0, 12) === "bibliography" && !form) { if ("string" === typeof initials) { return 1; @@ -17077,6 +22538,16 @@ CSL.Registry.NameReg = function (state) { var res = state.nameOutput.getName(nameobj, "locale-translit", true); nameobj = res.name; set_keys(this.state, "" + item_id, nameobj); + // + // possible options are: + // + //